@rero/ng-core
Version:
RERO angular core library.
16,324 lines • 745 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, inject, HostListener, Component, Pipe, Input, NgModule, input, output, effect, ViewChild, provideAppInitializer, ViewContainerRef, Directive, signal, computed, EventEmitter, Output, LOCALE_ID, model, ChangeDetectionStrategy, ChangeDetectorRef, viewChild } from '@angular/core';
import * as i1$2 from '@angular/common';
import { DatePipe, registerLocaleData, CommonModule, Location } from '@angular/common';
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import * as i2$8 from '@angular/router';
import { TitleStrategy, RouterModule, Router, ActivatedRoute } from '@angular/router';
import * as i5 from '@ngx-translate/core';
import { TranslateService, TranslateModule, TranslateLoader as TranslateLoader$1, _ } from '@ngx-translate/core';
import { NgxSpinnerModule, NgxSpinnerService } from 'ngx-spinner';
import { ConfirmationService, MessageService } from 'primeng/api';
import { of, forkJoin, throwError, Subject, BehaviorSubject, catchError as catchError$1, tap as tap$1, isObservable, map as map$5, Subscription, combineLatest, switchMap as switchMap$1, merge } from 'rxjs';
import { DynamicDialogConfig, DynamicDialogRef, DialogService, DynamicDialogModule } from 'primeng/dynamicdialog';
import * as i1 from 'primeng/button';
import { ButtonModule } from 'primeng/button';
import { DomSanitizer, Title } from '@angular/platform-browser';
import { marked } from 'marked';
import * as i1$3 from 'primeng/autocomplete';
import { AutoCompleteModule } from 'primeng/autocomplete';
import * as i1$1 from 'primeng/autofocus';
import { AutoFocusModule } from 'primeng/autofocus';
import { BadgeModule } from 'primeng/badge';
import { CardModule } from 'primeng/card';
import * as i2$5 from 'primeng/checkbox';
import { CheckboxModule } from 'primeng/checkbox';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import * as i2$4 from 'primeng/datepicker';
import { DatePickerModule } from 'primeng/datepicker';
import * as i2$3 from 'primeng/divider';
import { DividerModule } from 'primeng/divider';
import * as i1$7 from 'primeng/fieldset';
import { FieldsetModule } from 'primeng/fieldset';
import * as i4 from 'primeng/inputgroup';
import { InputGroupModule } from 'primeng/inputgroup';
import * as i3 from 'primeng/inputgroupaddon';
import { InputGroupAddonModule } from 'primeng/inputgroupaddon';
import * as i3$6 from 'primeng/inputnumber';
import { InputNumberModule } from 'primeng/inputnumber';
import * as i3$1 from 'primeng/inputtext';
import { InputTextModule } from 'primeng/inputtext';
import * as i2$2 from 'primeng/listbox';
import { ListboxModule } from 'primeng/listbox';
import * as i2$7 from 'primeng/menu';
import { MenuModule } from 'primeng/menu';
import * as i2$1 from 'primeng/message';
import { MessageModule } from 'primeng/message';
import * as i1$8 from 'primeng/paginator';
import { PaginatorModule } from 'primeng/paginator';
import { PanelModule } from 'primeng/panel';
import * as i1$6 from 'primeng/radiobutton';
import { RadioButtonModule } from 'primeng/radiobutton';
import { RippleModule } from 'primeng/ripple';
import { ScrollPanelModule } from 'primeng/scrollpanel';
import * as i4$2 from 'primeng/select';
import { SelectModule } from 'primeng/select';
import * as i3$3 from 'primeng/splitbutton';
import { SplitButtonModule } from 'primeng/splitbutton';
import * as i1$9 from 'primeng/tabs';
import { TabsModule } from 'primeng/tabs';
import * as i1$5 from 'primeng/textarea';
import { TextareaModule } from 'primeng/textarea';
import * as i2$6 from 'primeng/tieredmenu';
import { TieredMenuModule } from 'primeng/tieredmenu';
import { ToastModule } from 'primeng/toast';
import * as i1$4 from 'primeng/toggleswitch';
import { ToggleSwitchModule } from 'primeng/toggleswitch';
import * as i2 from 'primeng/tooltip';
import { TooltipModule } from 'primeng/tooltip';
import { FluidModule } from 'primeng/fluid';
import { catchError, map as map$4, tap, debounceTime, first, switchMap, finalize, distinctUntilChanged } from 'rxjs/operators';
import localeDe from '@angular/common/locales/de';
import localeEn from '@angular/common/locales/en-GB';
import localeFr from '@angular/common/locales/fr';
import localeIt from '@angular/common/locales/it';
import { DateTime } from 'luxon';
import de$6 from 'primelocale/de.json';
import en$6 from 'primelocale/en.json';
import fr$6 from 'primelocale/fr.json';
import it$6 from 'primelocale/it.json';
import { PrimeNG } from 'primeng/config';
import { definePreset } from '@primeng/themes';
import Aura from '@primeng/themes/aura';
import * as i3$2 from '@angular/forms';
import { UntypedFormGroup, FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms';
import { FormlyJsonschema } from '@ngx-formly/core/json-schema';
import { cloneDeep } from 'lodash-es';
import * as i4$1 from '@ngx-formly/core';
import { FieldType, FormlyModule, FieldArrayType, FieldWrapper, FORMLY_CONFIG } from '@ngx-formly/core';
import { FormlyFormFieldModule } from '@ngx-formly/primeng/form-field';
import { FormlySelectModule } from '@ngx-formly/core/select';
import * as i3$4 from 'primeng/multiselect';
import { MultiSelectModule } from 'primeng/multiselect';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormlySelectModule as FormlySelectModule$1 } from '@ngx-formly/primeng/select';
import * as i3$5 from 'primeng/treeselect';
import { TreeSelectModule } from 'primeng/treeselect';
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard';
import { GeneratePassword } from 'generate-password-lite';
import { FormlyPrimeNGModule } from '@ngx-formly/primeng';
import * as i8 from 'primeng/dataview';
import { DataViewModule } from 'primeng/dataview';
import EasyMDE from 'easymde';
import { DomHandler } from 'primeng/dom';
import * as CryptoJS from 'crypto-js';
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Service for managing configuration of the application.
*/
class CoreConfigService {
constructor() {
this.production = false;
this.apiBaseUrl = '';
this.apiEndpointPrefix = '/api';
this.schemaFormEndpoint = '/api/schemaform';
this.languages = ['en'];
this.defaultLanguage = 'en';
this.secretPassphrase = 'ShERWIN53SnAggIng48rELAtiVes';
this.translationsURLs = [];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CoreConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CoreConfigService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CoreConfigService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Service giving information about API.
*/
class ApiService {
constructor() {
this.configService = inject(CoreConfigService);
// API base URL. Ex: https://localhost:5000.
this.baseUrl = '';
// API prefix. Ex: /api
this.endpointPrefix = '/';
}
/**
* Returns Invenio API Endpoint corresponding to type.
*
* @param type Type of the resource.
* @param absolute If absolute or relative url must be returned.
* @return Endpoint as string.
*/
getEndpointByType(type, absolute = false) {
let endpoint = this.configService.apiEndpointPrefix + '/' + type;
if (absolute === true) {
endpoint = this.configService.apiBaseUrl + endpoint;
}
return endpoint;
}
/**
* Returns Invenio-RERO-base API export endpoint corresponding to type.
*
* @param type Type of the resource.
* @param absolute If absolute or relative url must be returned.
* @return Endpoint as string.
*/
getExportEndpointByType(type, absolute = false) {
let endpoint = this.configService.apiEndpointPrefix + '/export/' + type;
if (absolute === true) {
endpoint = this.configService.apiBaseUrl + endpoint;
}
return endpoint;
}
/**
* Returns $ref endpoint to resource.
*
* @param type Type of resource.
* @param id Id of the record.
* @return Ref endpoint as string.
*/
getRefEndpoint(type, id) {
let prefix = '';
if (this.configService.$refPrefix) {
prefix += this.configService.$refPrefix;
}
return `${prefix}${this.configService.apiEndpointPrefix}/${type}/${id}`;
}
/**
* Returns invenio API FormOption Endpoint corresponding to type.
*
* @param type Type of the resource
* @param absolute If absolute or relative url must be returned.
* @return Schema form endpoint.
*/
getSchemaFormEndpoint(type, absolute = false) {
let endpoint = this.configService.schemaFormEndpoint + '/' + type;
if (absolute === true) {
endpoint = this.configService.apiBaseUrl + endpoint;
}
return endpoint;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApiService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ApiService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2023 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Doc: https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
*
* The beforeunload event is fired when the window, the document and its resources
* are about to be unloaded. The document is still visible and the event is still
* cancelable at this point.
*/
class AbstractCanDeactivateComponent {
unloadNotification($event) {
if (!this.canDeactivate) {
$event.returnValue = true;
}
}
/**
* Can deactivate changed on editor
* @param activate - boolean
*/
canDeactivateChanged(activate) {
this.canDeactivate = activate;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AbstractCanDeactivateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: AbstractCanDeactivateComponent, isStandalone: false, selector: "ng-component", host: { listeners: { "window:beforeunload": "unloadNotification($event)" } }, ngImport: i0, template: '', isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AbstractCanDeactivateComponent, decorators: [{
type: Component,
args: [{
template: '',
standalone: false
}]
}], propDecorators: { unloadNotification: [{
type: HostListener,
args: ['window:beforeunload', ['$event']]
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Pipe for converting carriage returns to <br> html entities.
*/
class Nl2brPipe {
constructor() {
this.sanitizer = inject(DomSanitizer);
}
/**
* Returns transformed value containing <br> entities.
*
* @param value Value to transform.
* @return Transformed value.
*/
transform(value) {
if (!value) {
return this.sanitizer.bypassSecurityTrustHtml('');
}
return this.sanitizer.bypassSecurityTrustHtml(value.replace(/\r\n?|\n/g, '<br>\n'));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: Nl2brPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: Nl2brPipe, isStandalone: false, name: "nl2br" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: Nl2brPipe, decorators: [{
type: Pipe,
args: [{
name: 'nl2br',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class DialogComponent {
constructor() {
this.config = inject(DynamicDialogConfig);
this.ref = inject(DynamicDialogRef);
}
confirm() {
this.ref.close(true);
}
cancel() {
this.ref.close(false);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: DialogComponent, isStandalone: false, selector: "ng-core-dialog", ngImport: i0, template: `
<div class="core:flex core:flex-col core:gap-2">
<div class="core:flex" [innerHtml]="config.data.body|nl2br"></div>
<div class="core:flex core:justify-end core:gap-1">
<p-button
[label]="config.data.cancelTitleButton || 'Cancel'|translate"
severity="danger"
[outlined]="true"
(onClick)="cancel()"
/>
@if (config.data.confirmButton) {
<p-button [label]="config.data.confirmTitleButton || 'OK'|translate" (onClick)="confirm()" />
}
</div>
</div>
`, isInline: true, dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "pipe", type: Nl2brPipe, name: "nl2br" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DialogComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-dialog',
template: `
<div class="core:flex core:flex-col core:gap-2">
<div class="core:flex" [innerHtml]="config.data.body|nl2br"></div>
<div class="core:flex core:justify-end core:gap-1">
<p-button
[label]="config.data.cancelTitleButton || 'Cancel'|translate"
severity="danger"
[outlined]="true"
(onClick)="cancel()"
/>
@if (config.data.confirmButton) {
<p-button [label]="config.data.confirmTitleButton || 'OK'|translate" (onClick)="confirm()" />
}
</div>
</div>
`,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Pipe for returning a default value if the given value is null or undefined.
*/
class DefaultPipe {
/**
* Returns a default value.
*
* @param value Value to check.
* @param defaultValue Value to return if value is null.
* @return Value or default value.
*/
transform(value, defaultValue) {
return value || defaultValue;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DefaultPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: DefaultPipe, isStandalone: false, name: "default" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DefaultPipe, decorators: [{
type: Pipe,
args: [{
name: 'default', pure: true,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying errors.
*/
class ErrorComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ErrorComponent, isStandalone: false, selector: "ng-core-error", inputs: { error: "error" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div class=\"core:text-center core:my-8\">\n <p class=\"core:text-4xl\">\n <i class=\"fa fa-flash core:mr-4\"></i>\n <span class=\"core:text-muted-color\">{{ error.status }}</span>\n {{ error.title }}\n </p>\n <h5 class=\"core:mt-8\">{{ error.message | default: ('You cannot access this page.' | translate) }}</h5>\n</div>\n", dependencies: [{ kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "pipe", type: DefaultPipe, name: "default" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ErrorComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-error', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div class=\"core:text-center core:my-8\">\n <p class=\"core:text-4xl\">\n <i class=\"fa fa-flash core:mr-4\"></i>\n <span class=\"core:text-muted-color\">{{ error.status }}</span>\n {{ error.title }}\n </p>\n <h5 class=\"core:mt-8\">{{ error.message | default: ('You cannot access this page.' | translate) }}</h5>\n</div>\n" }]
}], propDecorators: { error: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2023-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* When this guard is configured, it intercepts the form output without
* saving or undoing changes.
*
* Route definition configuration
* { path: 'foo/bar', canDeactivate: [ ComponentCanDeactivateGuard ] },
*
* Custom editor component configuration
* class FooComponent AbstractCanDeactivateComponent {
* canDeactivate: boolean = false;
* ...
* }
*
* Template configuration (add output canDeactivateChange)
* <ng-core-editor
* (canDeactivateChange)="canDeactivateChanged($event)"
* ...
* ></ng-core-editor>
*/
class ComponentCanDeactivateGuard {
constructor() {
this.translateService = inject(TranslateService);
this.dialogService = inject(DialogService);
}
/**
* Displays a confirmation modal if the user leaves the form without
* saving or canceling
* @param component - AbstractCanDeactivateComponent
* @returns Observable<boolean> or boolean
*/
canDeactivate(component) {
if (!component.canDeactivate) {
this.ref = this.dialogService.open(DialogComponent, {
header: this.translateService.instant('Quit the page'),
data: {
body: this.translateService.instant('Do you want to quit the page? The changes made so far will be lost.'),
confirmButton: true,
confirmTitleButton: this.translateService.instant('Quit'),
cancelTitleButton: this.translateService.instant('Stay'),
},
dismissableMask: true,
modal: true,
style: { width: '25rem' }
});
return this.ref.onClose;
}
return true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ComponentCanDeactivateGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ComponentCanDeactivateGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ComponentCanDeactivateGuard, decorators: [{
type: Injectable
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Pipe for applying a callback function to filter array items.
*/
class CallbackArrayFilterPipe {
/**
* Filter array items with the given callback function.
*
* @param items List of items.
* @param callback Callback function to apply.
* @return List of filtered values.
*/
transform(items, callback) {
if (!items && !callback) {
return [];
}
if (items && !callback) {
return items;
}
return items.filter(item => callback(item));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CallbackArrayFilterPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: CallbackArrayFilterPipe, isStandalone: false, name: "callbackArrayFilter" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CallbackArrayFilterPipe, decorators: [{
type: Pipe,
args: [{
name: 'callbackArrayFilter',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Get human readable file size.
*/
class FilesizePipe {
/**
* Transform size to a human readable size.
*
* @param size Size of file.
* @param precision Number of decimals.
* @return Human readable file size.
*/
transform(size, precision = 2) {
let i = -1;
const byteUnits = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
do {
size /= 1024;
i++;
} while (size > 1024);
return [Math.max(size, 0.1).toFixed(precision), byteUnits[i]].join(' ');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FilesizePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: FilesizePipe, isStandalone: false, name: "filesize" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FilesizePipe, decorators: [{
type: Pipe,
args: [{
name: 'filesize',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2021-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Pipe to transform markdown to html.
*/
class MarkdownPipe {
constructor() {
this.sanitizer = inject(DomSanitizer);
}
/**
* Transform markdown to HTML.
*
* @param value Markdown initial value.
* @returns HTML corresponding to markdown.
*/
transform(value) {
const markValue = marked.parse(value, { gfm: true, breaks: false, async: false });
return this.sanitizer.bypassSecurityTrustHtml(markValue);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MarkdownPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: MarkdownPipe, isStandalone: false, name: "markdown" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MarkdownPipe, decorators: [{
type: Pipe,
args: [{
name: 'markdown',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class SanitizePipe {
transform(value, regex = '<[^>]*>', replace = '', flag = 'g') {
const re = new RegExp(regex, flag);
return value.replace(re, replace);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SanitizePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: SanitizePipe, isStandalone: false, name: "sanitize" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SanitizePipe, decorators: [{
type: Pipe,
args: [{
name: 'sanitize',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Source code: https://dev.to/codephobia/using-typescricore:pt-to-sort-by-keys-35ob
*/
/**
* Error messages
*/
const ERROR_REQUIRES_AT_LEAST_ONE_KEY = 'provide at least one key to sort by';
const ERROR_KEY_LENGTH_INVALID = 'a key was provided as an empty string';
const ERROR_DESC_KEY_LENGTH_INVALID = 'a descending key was missing the key name';
const ERROR_OBJECT_DOESNT_CONTAIN_KEY = 'a key you are attempting to sort by is not on all objects';
/**
* Recursive function to sort values by their keys.
*/
const sortByKey = (a, b, ...keys) => {
// Get first key in array.
let key = keys.shift();
// Make sure we have a valid key name.
if (!key.length) {
throw new Error(ERROR_KEY_LENGTH_INVALID);
}
// Default to ascending order.
let desc = false;
// Check for descending sort.
if (key.charAt(0) === '-') {
// Make sure key has a name as well as the minus sign.
if (key.length < 2) {
throw new Error(ERROR_DESC_KEY_LENGTH_INVALID);
}
// Remove minus from key name.
key = key.substr(1);
// Flag as descending order.
desc = true;
}
// Make sure the objects both have the key. We make sure
// to check this after we have removed the minus sign.
if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
throw new Error(ERROR_OBJECT_DOESNT_CONTAIN_KEY);
}
// Determine checks based on asc / desc.
const direction = (desc) ? -1 : 1;
// Perform bubble sort based on the values.
const authorizedSort = [-1, 1];
if (authorizedSort.includes(a[key].localeCompare(b[key]))) {
return a[key].localeCompare(b[key]) * direction;
}
// The values of the current key are equal, so if we still
// have keys to check recursively, check the next key.
if (keys.length) {
return sortByKey(a, b, ...keys);
}
// All keys returned and no more sorting needed.
return 0;
};
/**
* Wrapper sort function for the recursive one.
*/
const sortByKeys = (data, ...keys) => {
// Make sure we have at least one key to sort by.
if (!keys.length) {
throw new Error(ERROR_REQUIRES_AT_LEAST_ONE_KEY);
}
// Sort data.
data.sort((a, b) => {
return sortByKey(a, b, ...keys);
});
// Return sorted data.
return data;
};
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Source code: https://dev.to/codephobia/using-typescript-to-sort-by-keys-35ob
*/
/**
* Pipe for the sorting function.
*/
class SortByKeysPipe {
/**
* Order elements by sort key(s)
* @param value - array of values
* @param keys - string, sort key
*/
transform(value, ...keys) {
return sortByKeys(value, ...keys);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SortByKeysPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: SortByKeysPipe, isStandalone: false, name: "sortByKeys" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SortByKeysPipe, decorators: [{
type: Pipe,
args: [{
name: 'sortByKeys',
pure: true,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Pipe for truncating text.
*/
class TruncateTextPipe {
/**
* Truncate text.
*
* @param value Text to truncate.
* @param limit Limit after which the text is truncated.
* @param trail Trailing chars
* @param type Word or Char type
* @return Truncated string
*/
transform(value, limit = 40, trail = '…', type = 'word') {
let result = value || '';
if (type === 'char' && value) {
if (value.length < limit) {
return value;
}
else {
return value.substring(0, limit) + trail;
}
}
if (type === 'word' && value) {
const words = value.split(/\s+/);
if (words.length > Math.abs(limit)) {
if (limit < 0) {
limit *= -1;
result =
trail + words.slice(words.length - limit).join(' ');
}
else {
result = words.slice(0, limit).join(' ') + trail;
}
}
}
return result;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TruncateTextPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: TruncateTextPipe, isStandalone: false, name: "truncateText" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TruncateTextPipe, decorators: [{
type: Pipe,
args: [{
name: 'truncateText',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function extractIdOnRef(ref) {
if (ref != null) {
const pidRegExp = new RegExp('.*/(.*)$');
return pidRegExp.exec(ref)[1];
}
return null;
}
function cleanDictKeys(data) {
// let data = _.cloneDeep(data);
for (const key in data) {
if (data[key] === null || data[key] === undefined || data[key].length === 0) {
delete data[key];
}
}
return data;
}
function capitalize(value) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Uppercase the first letter of the string.
*/
class UpperCaseFirstPipe {
/**
* Uppercase the first letter of the given value.
*
* @param value Value to transform.
* @return Transformed value.
*/
transform(value) {
if (!value) {
return value;
}
return capitalize(value);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UpperCaseFirstPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: UpperCaseFirstPipe, isStandalone: false, name: "ucfirst" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UpperCaseFirstPipe, decorators: [{
type: Pipe,
args: [{
name: 'ucfirst',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class PrimeNgCoreModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PrimeNgCoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: PrimeNgCoreModule, exports: [AutoCompleteModule,
AutoFocusModule,
BadgeModule,
ButtonModule,
CardModule,
CheckboxModule,
ConfirmDialogModule,
DatePickerModule,
DividerModule,
DynamicDialogModule,
FieldsetModule,
FluidModule,
InputGroupAddonModule,
InputGroupModule,
InputNumberModule,
InputTextModule,
ListboxModule,
MenuModule,
MessageModule,
PaginatorModule,
PanelModule,
RadioButtonModule,
RippleModule,
ScrollPanelModule,
SelectModule,
SplitButtonModule,
TabsModule,
TextareaModule,
TieredMenuModule,
ToastModule,
ToggleSwitchModule,
TooltipModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PrimeNgCoreModule, providers: [
ConfirmationService,
DialogService,
MessageService
], imports: [AutoCompleteModule,
AutoFocusModule,
BadgeModule,
ButtonModule,
CardModule,
CheckboxModule,
ConfirmDialogModule,
DatePickerModule,
DividerModule,
DynamicDialogModule,
FieldsetModule,
FluidModule,
InputGroupAddonModule,
InputGroupModule,
InputNumberModule,
InputTextModule,
ListboxModule,
MenuModule,
MessageModule,
PaginatorModule,
PanelModule,
RadioButtonModule,
RippleModule,
ScrollPanelModule,
SelectModule,
SplitButtonModule,
TabsModule,
TextareaModule,
TieredMenuModule,
ToastModule,
ToggleSwitchModule,
TooltipModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PrimeNgCoreModule, decorators: [{
type: NgModule,
args: [{
providers: [
ConfirmationService,
DialogService,
MessageService
],
exports: [
AutoCompleteModule,
AutoFocusModule,
BadgeModule,
ButtonModule,
CardModule,
CheckboxModule,
ConfirmDialogModule,
DatePickerModule,
DividerModule,
DynamicDialogModule,
FieldsetModule,
FluidModule,
InputGroupAddonModule,
InputGroupModule,
InputNumberModule,
InputTextModule,
ListboxModule,
MenuModule,
MessageModule,
PaginatorModule,
PanelModule,
RadioButtonModule,
RippleModule,
ScrollPanelModule,
SelectModule,
SplitButtonModule,
TabsModule,
TextareaModule,
TieredMenuModule,
ToastModule,
ToggleSwitchModule,
TooltipModule,
],
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class SearchInputComponent {
constructor() {
/** Input Id */
this.inputId = input('search');
/** Display label */
this.displayLabel = input(false);
/** Placeholder */
this.placeholder = input('search');
/** Value to search */
this.searchText = input('');
/** Remove trailing and leading spaces if true */
this.trimQueryString = input(true);
/** Disabled attribute of search input */
this.disabled = input(false);
/** Make the focus on field */
this.focus = input(false);
/** Search value event */
this.search = output();
effect(() => {
if (this.focus() && this.input?.nativeElement) {
this.input.nativeElement.focus();
}
});
}
/**
* Start the search by pressing enter
* or clicking on the lens.
*/
doSearch() {
const { value } = this.input.nativeElement;
this.search.emit(this.trimQueryString() ? value.trim() : value);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SearchInputComponent, isStandalone: false, selector: "ng-core-search-input", inputs: { inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, displayLabel: { classPropertyName: "displayLabel", publicName: "displayLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, searchText: { classPropertyName: "searchText", publicName: "searchText", isSignal: true, isRequired: false, transformFunction: null }, trimQueryString: { classPropertyName: "trimQueryString", publicName: "trimQueryString", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, focus: { classPropertyName: "focus", publicName: "focus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { search: "search" }, viewQueries: [{ propertyName: "input", first: true, predicate: ["searchInput"], descendants: true }], ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (displayLabel()) {\n <label [for]=\"inputId()\" class=\"core:font-bold\">{{ placeholder() }}</label>\n}\n<p-inputGroup>\n <input\n #searchInput\n [pAutoFocus]=\"focus()\"\n pInputText\n [disabled]=\"disabled()\"\n [id]=\"inputId()\"\n [placeholder]=\"!displayLabel() && placeholder() ? placeholder() : ''\"\n [value]=\"searchText()\"\n (keydown.enter)=\"doSearch()\"\n attr.aria-label=\"{{ placeholder() }}\"\n />\n <p-inputGroupAddon>\n <p-button\n icon=\"fa fa-search\"\n variant=\"text\"\n severity=\"secondary\"\n (click)=\"doSearch()\"\n class=\"core:h-[2.5rem]\"\n />\n </p-inputGroupAddon>\n</p-inputGroup>\n", dependencies: [{ kind: "directive", type: i1$1.AutoFocus, selector: "[pAutoFocus]", inputs: ["autofocus", "pAutoFocus"] }, { kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "component", type: i4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["style", "styleClass"] }, { kind: "directive", type: i3$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchInputComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-search-input', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (displayLabel()) {\n <label [for]=\"inputId()\" class=\"core:font-bold\">{{ placeholder() }}</label>\n}\n<p-inputGroup>\n <input\n #searchInput\n [pAutoFocus]=\"focus()\"\n pInputText\n [disabled]=\"disabled()\"\n [id]=\"inputId()\"\n [placeholder]=\"!displayLabel() && placeholder() ? placeholder() : ''\"\n [value]=\"searchText()\"\n (keydown.enter)=\"doSearch()\"\n attr.aria-label=\"{{ placeholder() }}\"\n />\n <p-inputGroupAddon>\n <p-button\n icon=\"fa fa-search\"\n variant=\"text\"\n severity=\"secondary\"\n (click)=\"doSearch()\"\n class=\"core:h-[2.5rem]\"\n />\n </p-inputGroupAddon>\n</p-inputGroup>\n" }]
}], ctorParameters: () => [], propDecorators: { input: [{
type: ViewChild,
args: ['searchInput']
}] } });
/*
* RERO angular core
* Copyright (C) 2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class PageTitleStrategy extends TitleStrategy {
constructor() {
super(...arguments);
this.title = inject(Title);
this.config = inject(CoreConfigService);
this.translate = inject(TranslateService);
}
updateTitle(routerState) {
const title = this.buildTitle(routerState);
let projectTitle = '';
if (this.config.projectTitle) {
projectTitle = this.config.projectTitle;
}
if (projectTitle && title) {
this.title.setTitle(`${this.translate.instant(title)} | ${projectTitle}`);
}
else if (projectTitle) {
this.title.setTitle(projectTitle);
}
else if (title) {
this.title.setTitle(this.translate.instant(title));
}
else {
this.title.setTitle('-');
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PageTitleStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PageTitleStrategy, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PageTitleStrategy, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying a "read more" link after a text.
*/
class TextReadMoreComponent {
constructor() {
/** Number of parts to display (depending of the unit) */
this.limit = 50;
/** "Show more" label to display */
this.showMoreLabel = 'Show more';
/** "Show less" label to display */
this.showLessLabel = 'Show less';
/** Splitting unit [ word | character] */
this.unit = 'word';
/** trailing string */
this.trailing = '…';
/** boolean to know if initial text is expanded */
this.isExpanded = false;
/** boolean to know if limit is reached */
this.isLimitReached = true;
}
ngOnInit() {
this.textToDisplay = this.sTruncateText = this._truncateText();
if (this.text === this.textToDisplay) {
this.isLimitReached = false;
}
}
/**
* Allow to toggle content of the field
* @param event : Click event
*/
toggleText(event) {
event.preventDefault();
this.textToDisplay = (this.isExpanded)
? this.sTruncateText
: this.text;
this.isExpanded = !this.isExpanded;
}
/**
* Show the link label depending of isExpanded
*/
get linkLabel() {
return this.isExpanded ? this.showLessLabel : this.showMoreLabel;
}
/**
* Truncate initial text using the defined limit and unit.
*/
_truncateText() {
if (this.text === undefined) {
return '';
}
let pattern = /\s+/;
let joinText = ' ';
if (this.unit !== 'word') {
pattern = '';
joinText = '';
}
const parts = this.text.split(pattern);
if (parts.length <= this.limit) {
return this.text;
}
else {
if (this.limit < 0) {
const tmpLimit = this.limit * -1;
return this.trailing + parts.slice(parts.length - tmpLimit, parts.length).join(joinText);
}
else {
return parts.slice(0, this.limit).join(joinText) + this.trailing;
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TextReadMoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: TextReadMoreComponent, isStandalone: false, selector: "ng-core-text-read-more", inputs: { text: "text", limit: "limit", showMoreLabel: "showMoreLabel", showLessLabel: "showLessLabel", unit: "unit", trailing: "trailing" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (isLimitReached && limit<0) {\n <a class=\"core:pl-2\" href=\"#\" (click)=\"toggleText($event)\">{{ linkLabel }}</a>\n}\n<span [innerHTML]=\"textToDisplay | nl2br\"></span>\n@if (isLimitReached && limit>=0) {\n <a class=\"core:pl-2\" href=\"#\" (click)=\"toggleText($event)\">{{ linkLabel }}</a>\n}\n", dependencies: [{ kind: "pipe", type: Nl2brPipe, name: "nl2br" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TextReadMoreComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-text-read-more', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (isLimitReached && limit<0) {\n <a class=\"core:pl-2\" href=\"#\" (click)=\"toggleText($event)\">{{ linkLabel }}</a>\n}\n<span [innerHTML]=\"textToDisplay | nl2br\"></span>\n@if (isLimitReached && limit>=0) {\n <a class=\"core:pl-2\" href=\"#\" (click)=\"toggleText($event)\">{{ linkLabel }}</a>\n}\n" }]
}], propDecorators: { text: [{
type: Input
}], limit: [{
type: Input
}], showMoreLabel: [{
type: Input
}], showLessLabel: [{
type: Input
}], unit: [{
type: Input
}], trailing: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class DateTranslatePipe extends DatePipe {
constructor() {
super(...arguments);
this.translateService = inject(TranslateService);
}
transform(value, format, timezone, locale) {
if (!locale) {
locale = this.translateService.currentLang;
}
if (locale === 'en') {
locale = `${locale}-GB`;
}
return super.transform(value, format, timezone, locale);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DateTranslatePipe, deps: null, target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: DateTranslatePipe, isStandalone: false, name: "dateTranslate", pure: false }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DateTranslatePipe, decorators: [{
type: Pipe,
args: [{
name: 'dateTranslate',
pure: false,
standalone: false
}]
}] });
var aar$3 = "Afar";
var abk$3 = "Abchasisch";
var ace$3 = "Achinesisch";
var ach$3 = "Acholi";
var ada$3 = "Dangme";
var ady$3 = "Adygeisch";
var afa$3 = "Afroasiatische Sprachen";
var afh$3 = "Afrihili";
var afr$3 = "Afrikaans";
var ain$3 = "Ainu";
var aka$3 = "Akan";
var akk$3 = "Akkadisch";
var alb$3 = "Albanisch";
var ale$3 = "Aleutisch";
var alg$3 = "Algonkin Sprachen";
var alt$3 = "Südaltaisch";
var amh$3 = "Amharisch";
var ang$3 = "Altenglisch";
var anp$3 = "Angika";
var apa$3 = "Apache Sprachen";
var ara$3 = "Arabisch";
var arc$3 = "Reichsaramäisch";
var arg$3 = "Aragonesisch";
var arm$3 = "Armenisch";
var arn$3 = "Mapudungun";
var arp$3 = "Arapaho";
var art$3 = "Konstruierte Sprachen";
var arw$3 = "Arawak";
var asm$3 = "Assamesisch";
var ast$3 = "Asturisch";
var ath$3 = "Athapaskische Sprachen";
var aus$3 = "Australische Sprachen";
var ava$3 = "Awarisch";
var ave$3 = "Avestisch";
var awa$3 = "Awadhi";
var aym$3 = "Aymara";
var aze$3 = "Aserbaidschanisch";
var bad$3 = "Banda Sprachen";
var bai$3 = "Bamileke Sprachen";
var bak$3 = "Baschkirisch";
var bal$3 = "Belutschisch";
var bam$3 = "Bambara";
var ban$3 = "Balinesisch";
var baq$3 = "Baskisch";
var bas$3 = "Basaa";
var bat$3 = "Baltische Sprachen";
var bej$3 = "Bedscha";
var bel$3 = "Weißrussisch";
var bem$3 = "Bemba";
var ben$3 = "Bengalisch";
var ber$3 = "Berbersprachen";
var bho$3 = "Bhojpuri";
var bih$3 = "Bihari";
var bik$3 = "Bikolano";
var bin$3 = "Edo";
var bis$3 = "Bislama";
var bla$3 = "Blackfoot";
var bnt$3 = "Bantusprachen";
var bos$3 = "Bosnisch";
var bra$3 = "Braj-Bhakha";
var bre$3 = "Bretonisch";
var btk$3 = "Bataksprachen";
var bua$3 = "Burjatisch";
var bug$3 = "Buginesisch";
var bul$3 = "Bulgarisch";
var bur$3 = "Birmanisch";
var byn$3 = "Blin";
var cad$3 = "Caddo";
var cai$3 = "Mesoamerikanische Sprachen";
var car$3 = "Karib";
var cat$3 = "Katalanisch";
var cau$3 = "Kaukasische Sprachen";
var ceb$3 = "Cebuano";
var cel$3 = "Keltische Sprachen";
var cha$3 = "Chamorro";
var chb$3 = "Chibcha";
var che$3 = "Tschechisch";
var chg$3 = "Tschagataisch";
var chi$3 = "chinesisch";
var chk$3 = "Chuukesisch";
var chm$3 = "Mari";
var chn$3 = "Chinook Wawa";
var cho$3 = "Choctaw";
var chp$3 = "Chipewyan";
var chr$3 = "Cherokee";
var chu$3 = "Kirchenslawisch";
var chv$3 = "Tschuwaschisch";
var chy$3 = "Cheyenne";
var cmc$3 = "Chamische Sprachen";
var cnr$3 = "Montenegrinisch";
var cop$3 = "Koptisch";
var cor$3 = "Kornisch";
var cos$3 = "Korsisch";
var cpe$3 = "Englisch-basierte Kreols und Pidgins";
var cpf$3 = "Französisch-basierte Kreols und Pidgins";
var cpp$3 = "Portugiesisch-basierte Kreols und Pidgins";
var cre$3 = "Cree";
var crh$3 = "Krimtatarisch";
var crp$3 = "Kreol- und Pidginsprachen";
var csb$3 = "Kaschubisch";
var cus$3 = "Kuschitische Sprachen";
var cze$3 = "Tschechisch";
var dak$3 = "Dakota";
var dan$3 = "Dänisch";
var dar$3 = "Darginisch";
var day$3 = "Land-Dayak-Sprachen";
var del$3 = "Delawarisch";
var den$3 = "Slavey";
var dgr$3 = "Dogrib";
var din$3 = "Dinka";
var div$3 = "Dhivehi";
var doi$3 = "Dogri";
var dra$3 = "Dravidische Sprachen";
var dsb$3 = "Niedersorbisch";
var dua$3 = "Duala";
var dum$3 = "Mittelniederländisch";
var dut$3 = "Niederländisch";
var dyu$3 = "Dioula";
var dzo$3 = "Dzongkha";
var efi$3 = "Efik";
var egy$3 = "Ägyptisch";
var eka$3 = "Ekajuk";
var elx$3 = "Elamisch";
var eng$3 = "Englisch";
var enm$3 = "Mittelenglisch";
var epo$3 = "Esperanto";
var est$3 = "Estnisch";
var ewe$3 = "Ewe";
var ewo$3 = "Ewondo";
var fan$3 = "Fang";
var fao$3 = "Färöisch";
var fat$3 = "Fante";
var fij$3 = "Fidschi";
var fil$3 = "Filipino";
var fin$3 = "Finnisch";
var fiu$3 = "Finno-ugrische Sprachen";
var fon$3 = "Fon";
var fre$3 = "Französisch";
var frm$3 = "Mittelfranzösisch";
var fro$3 = "Altfranzösisch";
var frr$3 = "Nordfriesisch";
var frs$3 = "Ostfriesisch";
var fry$3 = "Westfriesisch";
var ful$3 = "Fulfulde";
var fur$3 = "Friulian";
var gaa$3 = "Ga";
var gay$3 = "Gayo";
var gba$3 = "Gbaya";
var gem$3 = "Germanische Sprachen";
var geo$3 = "Georgisch";
var ger$3 = "Deutsch";
var gez$3 = "Geez";
var gil$3 = "Kiribatisch";
var gla$3 = "Schottisch-gälisch";
var gle$3 = "Irisch";
var glg$3 = "Galicisch";
var glv$3 = "Manx";
var gmh$3 = "Mittelhochdeutsch";
var goh$3 = "Althochdeutsch";
var gon$3 = "Gondi";
var gor$3 = "Gorontalo";
var got$3 = "Gotisch";
var grb$3 = "Grebo";
var grc$3 = "Altgriechisch";
var gre$3 = "Griechisch";
var grn$3 = "Guarani";
var gsw$3 = "Schweizerdeutsch";
var guj$3 = "Gujarati";
var gwi$3 = "Gwich'in (Sprache)";
var hai$3 = "Haida";
var hat$3 = "Haitianisch";
var hau$3 = "Hausa";
var haw$3 = "Hawaiisch";
var heb$3 = "Hebräisch";
var her$3 = "Otjiherero";
var hil$3 = "Hiligaynon";
var him$3 = "West-Paharisprachen";
var hin$3 = "Hindi";
var hit$3 = "Hethitisch";
var hmn$3 = "Hmong-Sprache";
var hmo$3 = "Hiri-Motu";
var hrv$3 = "Kroatisch";
var hsb$3 = "Obersorbisch";
var hun$3 = "Ungarisch";
var hup$3 = "Hoopa";
var iba$3 = "Iban";
var ibo$3 = "Igbo";
var ice$3 = "Isländisch";
var ido$3 = "Ido";
var iii$3 = "Yi";
var ijo$3 = "Ijo-Sprachen";
var iku$3 = "Inuktitut";
var ile$3 = "Interlingue";
var ilo$3 = "Ilokano";
var ina$3 = "Interlingua";
var inc$3 = "Indoarische Sprachen";
var ind$3 = "Indonesisch";
var ine$3 = "Indogermanische Sprachen";
var inh$3 = "Inguschisch";
var ipk$3 = "Inupiaq";
var ira$3 = "Iranische Sprachen";
var iro$3 = "Irokesische Sprachen";
var ita$3 = "Italienisch";
var jav$3 = "Javanisch";
var jbo$3 = "Lojban";
var jpn$3 = "Japanisch";
var jpr$3 = "Judäo-Persisch";
var jrb$3 = "Judäo-Arabisch";
var kaa$3 = "Karakalpakisch";
var kab$3 = "Kabylisch";
var kac$3 = "Jingpo";
var kal$3 = "Grönländisch";
var kam$3 = "Kikamba";
var kan$3 = "Kannada";
var kar$3 = "Karenische Sprachen";
var kas$3 = "Kaschmiri";
var kau$3 = "Kanuri";
var kaw$3 = "Kawi";
var kaz$3 = "Kasachisch";
var kbd$3 = "Kabardinisch";
var kha$3 = "Khasi";
var khi$3 = "Khoisansprachen";
var khm$3 = "Khmer";
var kho$3 = "Khotanesisch";
var kik$3 = "Kikuyu";
var kin$3 = "Kinyarwanda";
var kir$3 = "Kirgisisch";
var kmb$3 = "Kimbundu";
var kok$3 = "Konkani";
var kom$3 = "Komi";
var kon$3 = "Kikongo";
var kor$3 = "Koreanisch";
var kos$3 = "Kosraeanisch";
var kpe$3 = "Kpelle";
var krc$3 = "Karatschai-balkarisch";
var krl$3 = "Karelisch";
var kro$3 = "Kru-Sprachen";
var kru$3 = "Kurukh";
var kua$3 = "oshiKwanyama";
var kum$3 = "Kumykisch";
var kur$3 = "Kurdisch";
var kut$3 = "Kutanaha";
var lad$3 = "Judenspanisch";
var lah$3 = "Lahnda";
var lam$3 = "Lamba";
var lao$3 = "Laotisch";
var lat$3 = "Latein";
var lav$3 = "Lettisch";
var lez$3 = "Lesgisch";
var lim$3 = "Limburgisch";
var lin$3 = "Lingála";
var lit$3 = "Litauisch";
var lol$3 = "Lomongo";
var loz$3 = "Lozi";
var ltz$3 = "Luxemburgisch";
var lua$3 = "Tschiluba";
var lub$3 = "Kiluba";
var lug$3 = "Luganda";
var lui$3 = "Luiseño";
var lun$3 = "Chilunda";
var luo$3 = "Luo";
var lus$3 = "Mizo";
var mac$3 = "Mazedonisch";
var mad$3 = "Maduresisch";
var mag$3 = "Magadhi";
var mah$3 = "Marschallesisch";
var mai$3 = "Maithili";
var mak$3 = "Makassar";
var mal$3 = "Malayalam";
var man$3 = "Manding";
var mao$3 = "Maori";
var map$3 = "Austronesische Sprachen";
var mar$3 = "Marathi";
var mas$3 = "Maa";
var may$3 = "Malaiisch";
var mdf$3 = "Mokschanisch";
var mdr$3 = "Mandar";
var men$3 = "Mende";
var mga$3 = "Mittelirisch";
var mic$3 = "Míkmawísimk";
var min$3 = "Minangkabauisch";
var mis$3 = "Verschiedene Sprachen";
var mkh$3 = "Mon-Khmer-Sprachen";
var mlg$3 = "Malagasy";
var mlt$3 = "Maltesisch";
var mnc$3 = "Mandschurisch";
var mni$3 = "Meithei";
var mno$3 = "Manobo Sprachen";
var moh$3 = "Mohawk";
var mon$3 = "Mongolisch";
var mos$3 = "Mòoré";
var mul$3 = "Mehrsprachig";
var mun$3 = "Munda-Sprachen";
var mus$3 = "Muskogee-Sprachen";
var mwl$3 = "Mirandés";
var mwr$3 = "Marwari";
var myn$3 = "Maya Sprachen";
var myv$3 = "Ersjanisch";
var nah$3 = "Nahuatl";
var nai$3 = "Nordamerikanische Sprachen";
var nap$3 = "Neapolitanisch";
var nau$3 = "Nauruisch";
var nav$3 = "Navajo";
var nbl$3 = "Süd-Ndebele";
var nde$3 = "Nord-Ndebele";
var ndo$3 = "Ndonga";
var nds$3 = "Niederdeutsch";
var nep$3 = "Nepali";
var nia$3 = "Nias";
var nic$3 = "Niger-Kongo-Sprachen";
var niu$3 = "Niueanisch";
var nno$3 = "Nynorsk";
var nob$3 = "Bokmål";
var nog$3 = "Nogaisch";
var non$3 = "Altnordisch";
var nor$3 = "Norwegisch";
var nqo$3 = "N’Ko";
var nso$3 = "Nord-Sotho";
var nub$3 = "Nubische Sprachen";
var nwc$3 = "Klassisches Newari";
var nya$3 = "Chichewa";
var nym$3 = "Nyamwesi";
var nyn$3 = "Runyankole";
var nyo$3 = "Runyoro";
var nzi$3 = "Nzema";
var oci$3 = "Okzitanisch";
var oji$3 = "Ojibwe";
var ori$3 = "Oriya";
var orm$3 = "Oromo";
var osa$3 = "Osage";
var oss$3 = "Ossetisch";
var ota$3 = "Osmanisch";
var oto$3 = "Oto-Pame-Sprachen";
var paa$3 = "Papuasprachen";
var pag$3 = "Pangasinensisch";
var pal$3 = "Mittelpersisch";
var pam$3 = "Kapampangan";
var pan$3 = "Panjabi";
var pap$3 = "Papiamentu";
var pau$3 = "Palauisch";
var peo$3 = "Altpersisch";
var per$3 = "Persisch";
var phi$3 = "Philippinische Sprachen";
var phn$3 = "Phönizisch";
var pli$3 = "Pali";
var pol$3 = "Polnisch";
var pon$3 = "Pohnpeanisch";
var por$3 = "Portugiesisch";
var pra$3 = "Prakrit";
var pro$3 = "Altokzitanisch";
var pus$3 = "Paschtunisch";
var que$3 = "Quechua";
var raj$3 = "Rajasthani";
var rap$3 = "Rapanui";
var rar$3 = "Rarotonganisch";
var roa$3 = "Romanische Sprachen";
var roh$3 = "Bündnerromanisch";
var rom$3 = "Romani";
var rum$3 = "Rumänisch";
var run$3 = "Kirundi";
var rup$3 = "Aromunisch";
var rus$3 = "Russisch";
var sad$3 = "Sandawe";
var sag$3 = "Sango";
var sah$3 = "Jakutisch";
var sai$3 = "Südamerikanische Sprachen";
var sal$3 = "Salish-Sprachen";
var sam$3 = "Samaritanisch";
var san$3 = "Sanskrit";
var sas$3 = "Sasak";
var sat$3 = "Santali";
var scn$3 = "Sizilianisch";
var sco$3 = "Scots";
var sel$3 = "Selkupisch";
var sem$3 = "Semitische Sprachen";
var sga$3 = "Altirisch";
var sgn$3 = "Gebärdensprachen";
var shn$3 = "Shan";
var sid$3 = "Sidama";
var sin$3 = "Singhalesisch";
var sio$3 = "Sioux-Sprachen";
var sit$3 = "Sinotibetische Sprachen";
var sla$3 = "Slawische Sprachen";
var slo$3 = "Slowakisch";
var slv$3 = "Slowenisch";
var sma$3 = "Südsamisch";
var sme$3 = "Nordsamisch";
var smi$3 = "Samische Sprachen";
var smj$3 = "Lulesamisch";
var smn$3 = "Inarisamisch";
var smo$3 = "Samoanisch";
var sms$3 = "Skoltsamisch";
var sna$3 = "Shona";
var snd$3 = "Sindhi";
var snk$3 = "Soninke";
var sog$3 = "Sogdisch";
var som$3 = "Somali";
var son$3 = "Songhai-Sprachen";
var sot$3 = "Sesotho";
var spa$3 = "Spanisch";
var srd$3 = "Sardisch";
var srn$3 = "Sranantongo";
var srp$3 = "Serbisch";
var srr$3 = "Serer";
var ssa$3 = "Nilosaharanische Sprachen";
var ssw$3 = "Swahili";
var suk$3 = "Sukuma";
var sun$3 = "Sundanesisch";
var sus$3 = "Susu";
var sux$3 = "Sumerisch";
var swa$3 = "Suaheli";
var swe$3 = "Schwedisch";
var syc$3 = "Syrisch";
var syr$3 = "Nordost-Neuaramäisch";
var tah$3 = "Tahitianisch";
var tai$3 = "Tai-Sprachen";
var tam$3 = "Tamil";
var tat$3 = "Tatarisch";
var tel$3 = "Telugu";
var tem$3 = "Temne";
var ter$3 = "Terena";
var tet$3 = "Tetum";
var tgk$3 = "Tadschikisch";
var tgl$3 = "Tagalog";
var tha$3 = "Thai";
var tib$3 = "Tibetisch";
var tig$3 = "Tigre";
var tir$3 = "Tigrinya";
var tiv$3 = "Tiv";
var tkl$3 = "Tokelauisch";
var tlh$3 = "Klingonisch";
var tli$3 = "Tlingit";
var tmh$3 = "Tuareg";
var tog$3 = "ChiTonga";
var ton$3 = "Tongaisch";
var tpi$3 = "Tok Pisin";
var tsi$3 = "Tsimshian";
var tsn$3 = "Setswana";
var tso$3 = "Xitsonga";
var tuk$3 = "Turkmenisch";
var tum$3 = "Tumbuka";
var tup$3 = "Tupí-Sprachen";
var tur$3 = "Türkisch";
var tut$3 = "Altaische Sprachen";
var tvl$3 = "Tuvaluisch";
var twi$3 = "Twi";
var tyv$3 = "Tuwinisch";
var udm$3 = "Udmurtisch";
var uga$3 = "Ugaritisch";
var uig$3 = "Uigurisch";
var ukr$3 = "Ukrainisch";
var umb$3 = "Umbundu";
var und$3 = "Unbekannte Sprache";
var urd$3 = "Urdu";
var uzb$3 = "Usbekisch";
var vai$3 = "Vai";
var ven$3 = "Tshivenda";
var vie$3 = "Vietnamesisch";
var vol$3 = "Volapük";
var vot$3 = "Wotisch";
var wak$3 = "Wakash-Sprachen";
var wal$3 = "Wolaytta";
var war$3 = "Wáray-Wáray";
var was$3 = "Washo";
var wel$3 = "Walisisch";
var wen$3 = "Sorbische Sprache";
var wln$3 = "Wallonisch";
var wol$3 = "Wolof";
var xal$3 = "Kalmückisch";
var xho$3 = "IsiXhosa";
var yao$3 = "Yao";
var yap$3 = "Yapesisch";
var yid$3 = "Jiddisch";
var yor$3 = "Yoruba";
var ypk$3 = "Yupik-Sprachen";
var zap$3 = "Zapotekisch";
var zbl$3 = "Bliss-Symbole";
var zen$3 = "Zenaga";
var zha$3 = "Zhuang";
var znd$3 = "Zande-Sprachen";
var zul$3 = "isiZulu";
var zun$3 = "Zuñi";
var zxx$3 = "Keine Sprachinhalte";
var zza$3 = "Zazaisch";
var de$5 = {
aar: aar$3,
abk: abk$3,
ace: ace$3,
ach: ach$3,
ada: ada$3,
ady: ady$3,
afa: afa$3,
afh: afh$3,
afr: afr$3,
ain: ain$3,
aka: aka$3,
akk: akk$3,
alb: alb$3,
ale: ale$3,
alg: alg$3,
alt: alt$3,
amh: amh$3,
ang: ang$3,
anp: anp$3,
apa: apa$3,
ara: ara$3,
arc: arc$3,
arg: arg$3,
arm: arm$3,
arn: arn$3,
arp: arp$3,
art: art$3,
arw: arw$3,
asm: asm$3,
ast: ast$3,
ath: ath$3,
aus: aus$3,
ava: ava$3,
ave: ave$3,
awa: awa$3,
aym: aym$3,
aze: aze$3,
bad: bad$3,
bai: bai$3,
bak: bak$3,
bal: bal$3,
bam: bam$3,
ban: ban$3,
baq: baq$3,
bas: bas$3,
bat: bat$3,
bej: bej$3,
bel: bel$3,
bem: bem$3,
ben: ben$3,
ber: ber$3,
bho: bho$3,
bih: bih$3,
bik: bik$3,
bin: bin$3,
bis: bis$3,
bla: bla$3,
bnt: bnt$3,
bos: bos$3,
bra: bra$3,
bre: bre$3,
btk: btk$3,
bua: bua$3,
bug: bug$3,
bul: bul$3,
bur: bur$3,
byn: byn$3,
cad: cad$3,
cai: cai$3,
car: car$3,
cat: cat$3,
cau: cau$3,
ceb: ceb$3,
cel: cel$3,
cha: cha$3,
chb: chb$3,
che: che$3,
chg: chg$3,
chi: chi$3,
chk: chk$3,
chm: chm$3,
chn: chn$3,
cho: cho$3,
chp: chp$3,
chr: chr$3,
chu: chu$3,
chv: chv$3,
chy: chy$3,
cmc: cmc$3,
cnr: cnr$3,
cop: cop$3,
cor: cor$3,
cos: cos$3,
cpe: cpe$3,
cpf: cpf$3,
cpp: cpp$3,
cre: cre$3,
crh: crh$3,
crp: crp$3,
csb: csb$3,
cus: cus$3,
cze: cze$3,
dak: dak$3,
dan: dan$3,
dar: dar$3,
day: day$3,
del: del$3,
den: den$3,
dgr: dgr$3,
din: din$3,
div: div$3,
doi: doi$3,
dra: dra$3,
dsb: dsb$3,
dua: dua$3,
dum: dum$3,
dut: dut$3,
dyu: dyu$3,
dzo: dzo$3,
efi: efi$3,
egy: egy$3,
eka: eka$3,
elx: elx$3,
eng: eng$3,
enm: enm$3,
epo: epo$3,
est: est$3,
ewe: ewe$3,
ewo: ewo$3,
fan: fan$3,
fao: fao$3,
fat: fat$3,
fij: fij$3,
fil: fil$3,
fin: fin$3,
fiu: fiu$3,
fon: fon$3,
fre: fre$3,
frm: frm$3,
fro: fro$3,
frr: frr$3,
frs: frs$3,
fry: fry$3,
ful: ful$3,
fur: fur$3,
gaa: gaa$3,
gay: gay$3,
gba: gba$3,
gem: gem$3,
geo: geo$3,
ger: ger$3,
gez: gez$3,
gil: gil$3,
gla: gla$3,
gle: gle$3,
glg: glg$3,
glv: glv$3,
gmh: gmh$3,
goh: goh$3,
gon: gon$3,
gor: gor$3,
got: got$3,
grb: grb$3,
grc: grc$3,
gre: gre$3,
grn: grn$3,
gsw: gsw$3,
guj: guj$3,
gwi: gwi$3,
hai: hai$3,
hat: hat$3,
hau: hau$3,
haw: haw$3,
heb: heb$3,
her: her$3,
hil: hil$3,
him: him$3,
hin: hin$3,
hit: hit$3,
hmn: hmn$3,
hmo: hmo$3,
hrv: hrv$3,
hsb: hsb$3,
hun: hun$3,
hup: hup$3,
iba: iba$3,
ibo: ibo$3,
ice: ice$3,
ido: ido$3,
iii: iii$3,
ijo: ijo$3,
iku: iku$3,
ile: ile$3,
ilo: ilo$3,
ina: ina$3,
inc: inc$3,
ind: ind$3,
ine: ine$3,
inh: inh$3,
ipk: ipk$3,
ira: ira$3,
iro: iro$3,
ita: ita$3,
jav: jav$3,
jbo: jbo$3,
jpn: jpn$3,
jpr: jpr$3,
jrb: jrb$3,
kaa: kaa$3,
kab: kab$3,
kac: kac$3,
kal: kal$3,
kam: kam$3,
kan: kan$3,
kar: kar$3,
kas: kas$3,
kau: kau$3,
kaw: kaw$3,
kaz: kaz$3,
kbd: kbd$3,
kha: kha$3,
khi: khi$3,
khm: khm$3,
kho: kho$3,
kik: kik$3,
kin: kin$3,
kir: kir$3,
kmb: kmb$3,
kok: kok$3,
kom: kom$3,
kon: kon$3,
kor: kor$3,
kos: kos$3,
kpe: kpe$3,
krc: krc$3,
krl: krl$3,
kro: kro$3,
kru: kru$3,
kua: kua$3,
kum: kum$3,
kur: kur$3,
kut: kut$3,
lad: lad$3,
lah: lah$3,
lam: lam$3,
lao: lao$3,
lat: lat$3,
lav: lav$3,
lez: lez$3,
lim: lim$3,
lin: lin$3,
lit: lit$3,
lol: lol$3,
loz: loz$3,
ltz: ltz$3,
lua: lua$3,
lub: lub$3,
lug: lug$3,
lui: lui$3,
lun: lun$3,
luo: luo$3,
lus: lus$3,
mac: mac$3,
mad: mad$3,
mag: mag$3,
mah: mah$3,
mai: mai$3,
mak: mak$3,
mal: mal$3,
man: man$3,
mao: mao$3,
map: map$3,
mar: mar$3,
mas: mas$3,
may: may$3,
mdf: mdf$3,
mdr: mdr$3,
men: men$3,
mga: mga$3,
mic: mic$3,
min: min$3,
mis: mis$3,
mkh: mkh$3,
mlg: mlg$3,
mlt: mlt$3,
mnc: mnc$3,
mni: mni$3,
mno: mno$3,
moh: moh$3,
mon: mon$3,
mos: mos$3,
mul: mul$3,
mun: mun$3,
mus: mus$3,
mwl: mwl$3,
mwr: mwr$3,
myn: myn$3,
myv: myv$3,
nah: nah$3,
nai: nai$3,
nap: nap$3,
nau: nau$3,
nav: nav$3,
nbl: nbl$3,
nde: nde$3,
ndo: ndo$3,
nds: nds$3,
nep: nep$3,
"new": "Newari",
nia: nia$3,
nic: nic$3,
niu: niu$3,
nno: nno$3,
nob: nob$3,
nog: nog$3,
non: non$3,
nor: nor$3,
nqo: nqo$3,
nso: nso$3,
nub: nub$3,
nwc: nwc$3,
nya: nya$3,
nym: nym$3,
nyn: nyn$3,
nyo: nyo$3,
nzi: nzi$3,
oci: oci$3,
oji: oji$3,
ori: ori$3,
orm: orm$3,
osa: osa$3,
oss: oss$3,
ota: ota$3,
oto: oto$3,
paa: paa$3,
pag: pag$3,
pal: pal$3,
pam: pam$3,
pan: pan$3,
pap: pap$3,
pau: pau$3,
peo: peo$3,
per: per$3,
phi: phi$3,
phn: phn$3,
pli: pli$3,
pol: pol$3,
pon: pon$3,
por: por$3,
pra: pra$3,
pro: pro$3,
pus: pus$3,
que: que$3,
raj: raj$3,
rap: rap$3,
rar: rar$3,
roa: roa$3,
roh: roh$3,
rom: rom$3,
rum: rum$3,
run: run$3,
rup: rup$3,
rus: rus$3,
sad: sad$3,
sag: sag$3,
sah: sah$3,
sai: sai$3,
sal: sal$3,
sam: sam$3,
san: san$3,
sas: sas$3,
sat: sat$3,
scn: scn$3,
sco: sco$3,
sel: sel$3,
sem: sem$3,
sga: sga$3,
sgn: sgn$3,
shn: shn$3,
sid: sid$3,
sin: sin$3,
sio: sio$3,
sit: sit$3,
sla: sla$3,
slo: slo$3,
slv: slv$3,
sma: sma$3,
sme: sme$3,
smi: smi$3,
smj: smj$3,
smn: smn$3,
smo: smo$3,
sms: sms$3,
sna: sna$3,
snd: snd$3,
snk: snk$3,
sog: sog$3,
som: som$3,
son: son$3,
sot: sot$3,
spa: spa$3,
srd: srd$3,
srn: srn$3,
srp: srp$3,
srr: srr$3,
ssa: ssa$3,
ssw: ssw$3,
suk: suk$3,
sun: sun$3,
sus: sus$3,
sux: sux$3,
swa: swa$3,
swe: swe$3,
syc: syc$3,
syr: syr$3,
tah: tah$3,
tai: tai$3,
tam: tam$3,
tat: tat$3,
tel: tel$3,
tem: tem$3,
ter: ter$3,
tet: tet$3,
tgk: tgk$3,
tgl: tgl$3,
tha: tha$3,
tib: tib$3,
tig: tig$3,
tir: tir$3,
tiv: tiv$3,
tkl: tkl$3,
tlh: tlh$3,
tli: tli$3,
tmh: tmh$3,
tog: tog$3,
ton: ton$3,
tpi: tpi$3,
tsi: tsi$3,
tsn: tsn$3,
tso: tso$3,
tuk: tuk$3,
tum: tum$3,
tup: tup$3,
tur: tur$3,
tut: tut$3,
tvl: tvl$3,
twi: twi$3,
tyv: tyv$3,
udm: udm$3,
uga: uga$3,
uig: uig$3,
ukr: ukr$3,
umb: umb$3,
und: und$3,
urd: urd$3,
uzb: uzb$3,
vai: vai$3,
ven: ven$3,
vie: vie$3,
vol: vol$3,
vot: vot$3,
wak: wak$3,
wal: wal$3,
war: war$3,
was: was$3,
wel: wel$3,
wen: wen$3,
wln: wln$3,
wol: wol$3,
xal: xal$3,
xho: xho$3,
yao: yao$3,
yap: yap$3,
yid: yid$3,
yor: yor$3,
ypk: ypk$3,
zap: zap$3,
zbl: zbl$3,
zen: zen$3,
zha: zha$3,
znd: znd$3,
zul: zul$3,
zun: zun$3,
zxx: zxx$3,
zza: zza$3
};
var aar$2 = "Afar";
var abk$2 = "Abkhaz";
var ace$2 = "Achinese";
var ach$2 = "Acoli";
var ada$2 = "Adangme";
var ady$2 = "Adygei";
var afa$2 = "Afroasiatic (Other)";
var afh$2 = "Afrihili (Artificial language)";
var afr$2 = "Afrikaans";
var ain$2 = "Ainu";
var aka$2 = "Akan";
var akk$2 = "Akkadian";
var alb$2 = "Albanian";
var ale$2 = "Aleut";
var alg$2 = "Algonquian (Other)";
var alt$2 = "Altai";
var amh$2 = "Amharic";
var ang$2 = "English, Old (ca. 450-1100)";
var anp$2 = "Angika";
var apa$2 = "Apache languages";
var ara$2 = "Arabic";
var arc$2 = "Aramaic";
var arg$2 = "Aragonese";
var arm$2 = "Armenian";
var arn$2 = "Mapuche";
var arp$2 = "Arapaho";
var art$2 = "Artificial (Other)";
var arw$2 = "Arawak";
var asm$2 = "Assamese";
var ast$2 = "Bable";
var ath$2 = "Athapascan (Other)";
var aus$2 = "Australian languages";
var ava$2 = "Avaric";
var ave$2 = "Avestan";
var awa$2 = "Awadhi";
var aym$2 = "Aymara";
var aze$2 = "Azerbaijani";
var bad$2 = "Banda languages";
var bai$2 = "Bamileke languages";
var bak$2 = "Bashkir";
var bal$2 = "Baluchi";
var bam$2 = "Bambara";
var ban$2 = "Balinese";
var baq$2 = "Basque";
var bas$2 = "Basa";
var bat$2 = "Baltic (Other)";
var bej$2 = "Beja";
var bel$2 = "Belarusian";
var bem$2 = "Bemba";
var ben$2 = "Bengali";
var ber$2 = "Berber (Other)";
var bho$2 = "Bhojpuri";
var bih$2 = "Bihari (Other)";
var bik$2 = "Bikol";
var bin$2 = "Edo";
var bis$2 = "Bislama";
var bla$2 = "Siksika";
var bnt$2 = "Bantu (Other)";
var bos$2 = "Bosnian";
var bra$2 = "Braj";
var bre$2 = "Breton";
var btk$2 = "Batak";
var bua$2 = "Buriat";
var bug$2 = "Bugis";
var bul$2 = "Bulgarian";
var bur$2 = "Burmese";
var byn$2 = "Bilin";
var cad$2 = "Caddo";
var cai$2 = "Central American Indian (Other)";
var car$2 = "Carib";
var cat$2 = "Catalan";
var cau$2 = "Caucasian (Other)";
var ceb$2 = "Cebuano";
var cel$2 = "Celtic (Other)";
var cha$2 = "Chamorro";
var chb$2 = "Chibcha";
var che$2 = "Chechen";
var chg$2 = "Chagatai";
var chi$2 = "Chinese";
var chk$2 = "Chuukese";
var chm$2 = "Mari";
var chn$2 = "Chinook jargon";
var cho$2 = "Choctaw";
var chp$2 = "Chipewyan";
var chr$2 = "Cherokee";
var chu$2 = "Church Slavic";
var chv$2 = "Chuvash";
var chy$2 = "Cheyenne";
var cmc$2 = "Chamic languages";
var cnr$2 = "Montenegrin";
var cop$2 = "Coptic";
var cor$2 = "Cornish";
var cos$2 = "Corsican";
var cpe$2 = "Creoles and Pidgins, English-based (Other)";
var cpf$2 = "Creoles and Pidgins, French-based (Other)";
var cpp$2 = "Creoles and Pidgins, Portuguese-based (Other)";
var cre$2 = "Cree";
var crh$2 = "Crimean Tatar";
var crp$2 = "Creoles and Pidgins (Other)";
var csb$2 = "Kashubian";
var cus$2 = "Cushitic (Other)";
var cze$2 = "Czech";
var dak$2 = "Dakota";
var dan$2 = "Danish";
var dar$2 = "Dargwa";
var day$2 = "Dayak";
var del$2 = "Delaware";
var den$2 = "Slavey";
var dgr$2 = "Dogrib";
var din$2 = "Dinka";
var div$2 = "Divehi";
var doi$2 = "Dogri";
var dra$2 = "Dravidian (Other)";
var dsb$2 = "Lower Sorbian";
var dua$2 = "Duala";
var dum$2 = "Dutch, Middle (ca. 1050-1350)";
var dut$2 = "Dutch";
var dyu$2 = "Dyula";
var dzo$2 = "Dzongkha";
var efi$2 = "Efik";
var egy$2 = "Egyptian";
var eka$2 = "Ekajuk";
var elx$2 = "Elamite";
var eng$2 = "English";
var enm$2 = "English, Middle (1100-1500)";
var epo$2 = "Esperanto";
var est$2 = "Estonian";
var ewe$2 = "Ewe";
var ewo$2 = "Ewondo";
var fan$2 = "Fang";
var fao$2 = "Faroese";
var fat$2 = "Fanti";
var fij$2 = "Fijian";
var fil$2 = "Filipino";
var fin$2 = "Finnish";
var fiu$2 = "Finno-Ugrian (Other)";
var fon$2 = "Fon";
var fre$2 = "French";
var frm$2 = "French, Middle (ca. 1300-1600)";
var fro$2 = "French, Old (ca. 842-1300)";
var frr$2 = "North Frisian";
var frs$2 = "East Frisian";
var fry$2 = "Frisian";
var ful$2 = "Fula";
var fur$2 = "Friulian";
var gaa$2 = "Gã";
var gay$2 = "Gayo";
var gba$2 = "Gbaya";
var gem$2 = "Germanic (Other)";
var geo$2 = "Georgian";
var ger$2 = "German";
var gez$2 = "Ethiopic";
var gil$2 = "Gilbertese";
var gla$2 = "Scottish Gaelic";
var gle$2 = "Irish";
var glg$2 = "Galician";
var glv$2 = "Manx";
var gmh$2 = "German, Middle High (ca. 1050-1500)";
var goh$2 = "German, Old High (ca. 750-1050)";
var gon$2 = "Gondi";
var gor$2 = "Gorontalo";
var got$2 = "Gothic";
var grb$2 = "Grebo";
var grc$2 = "Greek, Ancient (to 1453)";
var gre$2 = "Greek, Modern (1453- )";
var grn$2 = "Guarani";
var gsw$2 = "Swiss German";
var guj$2 = "Gujarati";
var gwi$2 = "Gwich'in";
var hai$2 = "Haida";
var hat$2 = "Haitian French Creole";
var hau$2 = "Hausa";
var haw$2 = "Hawaiian";
var heb$2 = "Hebrew";
var her$2 = "Herero";
var hil$2 = "Hiligaynon";
var him$2 = "Western Pahari languages";
var hin$2 = "Hindi";
var hit$2 = "Hittite";
var hmn$2 = "Hmong";
var hmo$2 = "Hiri Motu";
var hrv$2 = "Croatian";
var hsb$2 = "Upper Sorbian";
var hun$2 = "Hungarian";
var hup$2 = "Hupa";
var iba$2 = "Iban";
var ibo$2 = "Igbo";
var ice$2 = "Icelandic";
var ido$2 = "Ido";
var iii$2 = "Sichuan Yi";
var ijo$2 = "Ijo";
var iku$2 = "Inuktitut";
var ile$2 = "Interlingue";
var ilo$2 = "Iloko";
var ina$2 = "Interlingua (International Auxiliary Language Association)";
var inc$2 = "Indic (Other)";
var ind$2 = "Indonesian";
var ine$2 = "Indo-European (Other)";
var inh$2 = "Ingush";
var ipk$2 = "Inupiaq";
var ira$2 = "Iranian (Other)";
var iro$2 = "Iroquoian (Other)";
var ita$2 = "Italian";
var jav$2 = "Javanese";
var jbo$2 = "Lojban (Artificial language)";
var jpn$2 = "Japanese";
var jpr$2 = "Judeo-Persian";
var jrb$2 = "Judeo-Arabic";
var kaa$2 = "Kara-Kalpak";
var kab$2 = "Kabyle";
var kac$2 = "Kachin";
var kal$2 = "Kalâtdlisut";
var kam$2 = "Kamba";
var kan$2 = "Kannada";
var kar$2 = "Karen languages";
var kas$2 = "Kashmiri";
var kau$2 = "Kanuri";
var kaw$2 = "Kawi";
var kaz$2 = "Kazakh";
var kbd$2 = "Kabardian";
var kha$2 = "Khasi";
var khi$2 = "Khoisan (Other)";
var khm$2 = "Khmer";
var kho$2 = "Khotanese";
var kik$2 = "Kikuyu";
var kin$2 = "Kinyarwanda";
var kir$2 = "Kyrgyz";
var kmb$2 = "Kimbundu";
var kok$2 = "Konkani";
var kom$2 = "Komi";
var kon$2 = "Kongo";
var kor$2 = "Korean";
var kos$2 = "Kosraean";
var kpe$2 = "Kpelle";
var krc$2 = "Karachay-Balkar";
var krl$2 = "Karelian";
var kro$2 = "Kru (Other)";
var kru$2 = "Kurukh";
var kua$2 = "Kuanyama";
var kum$2 = "Kumyk";
var kur$2 = "Kurdish";
var kut$2 = "Kootenai";
var lad$2 = "Ladino";
var lah$2 = "Lahndā";
var lam$2 = "Lamba (Zambia and Congo)";
var lao$2 = "Lao";
var lat$2 = "Latin";
var lav$2 = "Latvian";
var lez$2 = "Lezgian";
var lim$2 = "Limburgish";
var lin$2 = "Lingala";
var lit$2 = "Lithuanian";
var lol$2 = "Mongo-Nkundu";
var loz$2 = "Lozi";
var ltz$2 = "Luxembourgish";
var lua$2 = "Luba-Lulua";
var lub$2 = "Luba-Katanga";
var lug$2 = "Ganda";
var lui$2 = "Luiseño";
var lun$2 = "Lunda";
var luo$2 = "Luo (Kenya and Tanzania)";
var lus$2 = "Lushai";
var mac$2 = "Macedonian";
var mad$2 = "Madurese";
var mag$2 = "Magahi";
var mah$2 = "Marshallese";
var mai$2 = "Maithili";
var mak$2 = "Makasar";
var mal$2 = "Malayalam";
var man$2 = "Mandingo";
var mao$2 = "Maori";
var map$2 = "Austronesian (Other)";
var mar$2 = "Marathi";
var mas$2 = "Maasai";
var may$2 = "Malay";
var mdf$2 = "Moksha";
var mdr$2 = "Mandar";
var men$2 = "Mende";
var mga$2 = "Irish, Middle (ca. 1100-1550)";
var mic$2 = "Micmac";
var min$2 = "Minangkabau";
var mis$2 = "Miscellaneous languages";
var mkh$2 = "Mon-Khmer (Other)";
var mlg$2 = "Malagasy";
var mlt$2 = "Maltese";
var mnc$2 = "Manchu";
var mni$2 = "Manipuri";
var mno$2 = "Manobo languages";
var moh$2 = "Mohawk";
var mon$2 = "Mongolian";
var mos$2 = "Mooré";
var mul$2 = "Multiple languages";
var mun$2 = "Munda (Other)";
var mus$2 = "Creek";
var mwl$2 = "Mirandese";
var mwr$2 = "Marwari";
var myn$2 = "Mayan languages";
var myv$2 = "Erzya";
var nah$2 = "Nahuatl";
var nai$2 = "North American Indian (Other)";
var nap$2 = "Neapolitan Italian";
var nau$2 = "Nauru";
var nav$2 = "Navajo";
var nbl$2 = "Ndebele (South Africa)";
var nde$2 = "Ndebele (Zimbabwe)";
var ndo$2 = "Ndonga";
var nds$2 = "Low German";
var nep$2 = "Nepali";
var nia$2 = "Nias";
var nic$2 = "Niger-Kordofanian (Other)";
var niu$2 = "Niuean";
var nno$2 = "Norwegian (Nynorsk)";
var nob$2 = "Norwegian (Bokmål)";
var nog$2 = "Nogai";
var non$2 = "Old Norse";
var nor$2 = "Norwegian";
var nqo$2 = "N'Ko";
var nso$2 = "Northern Sotho";
var nub$2 = "Nubian languages";
var nwc$2 = "Newari, Old";
var nya$2 = "Nyanja";
var nym$2 = "Nyamwezi";
var nyn$2 = "Nyankole";
var nyo$2 = "Nyoro";
var nzi$2 = "Nzima";
var oci$2 = "Occitan (post-1500)";
var oji$2 = "Ojibwa";
var ori$2 = "Oriya";
var orm$2 = "Oromo";
var osa$2 = "Osage";
var oss$2 = "Ossetic";
var ota$2 = "Turkish, Ottoman";
var oto$2 = "Otomian languages";
var paa$2 = "Papuan (Other)";
var pag$2 = "Pangasinan";
var pal$2 = "Pahlavi";
var pam$2 = "Pampanga";
var pan$2 = "Panjabi";
var pap$2 = "Papiamento";
var pau$2 = "Palauan";
var peo$2 = "Old Persian (ca. 600-400 B.C.)";
var per$2 = "Persian";
var phi$2 = "Philippine (Other)";
var phn$2 = "Phoenician";
var pli$2 = "Pali";
var pol$2 = "Polish";
var pon$2 = "Pohnpeian";
var por$2 = "Portuguese";
var pra$2 = "Prakrit languages";
var pro$2 = "Provençal (to 1500)";
var pus$2 = "Pushto";
var que$2 = "Quechua";
var raj$2 = "Rajasthani";
var rap$2 = "Rapanui";
var rar$2 = "Rarotongan";
var roa$2 = "Romance (Other)";
var roh$2 = "Raeto-Romance";
var rom$2 = "Romani";
var rum$2 = "Romanian";
var run$2 = "Rundi";
var rup$2 = "Aromanian";
var rus$2 = "Russian";
var sad$2 = "Sandawe";
var sag$2 = "Sango (Ubangi Creole)";
var sah$2 = "Yakut";
var sai$2 = "South American Indian (Other)";
var sal$2 = "Salishan languages";
var sam$2 = "Samaritan Aramaic";
var san$2 = "Sanskrit";
var sas$2 = "Sasak";
var sat$2 = "Santali";
var scn$2 = "Sicilian Italian";
var sco$2 = "Scots";
var sel$2 = "Selkup";
var sem$2 = "Semitic (Other)";
var sga$2 = "Irish, Old (to 1100)";
var sgn$2 = "Sign languages";
var shn$2 = "Shan";
var sid$2 = "Sidamo";
var sin$2 = "Sinhalese";
var sio$2 = "Siouan (Other)";
var sit$2 = "Sino-Tibetan (Other)";
var sla$2 = "Slavic (Other)";
var slo$2 = "Slovak";
var slv$2 = "Slovenian";
var sma$2 = "Southern Sami";
var sme$2 = "Northern Sami";
var smi$2 = "Sami";
var smj$2 = "Lule Sami";
var smn$2 = "Inari Sami";
var smo$2 = "Samoan";
var sms$2 = "Skolt Sami";
var sna$2 = "Shona";
var snd$2 = "Sindhi";
var snk$2 = "Soninke";
var sog$2 = "Sogdian";
var som$2 = "Somali";
var son$2 = "Songhai";
var sot$2 = "Sotho";
var spa$2 = "Spanish";
var srd$2 = "Sardinian";
var srn$2 = "Sranan";
var srp$2 = "Serbian";
var srr$2 = "Serer";
var ssa$2 = "Nilo-Saharan (Other)";
var ssw$2 = "Swazi";
var suk$2 = "Sukuma";
var sun$2 = "Sundanese";
var sus$2 = "Susu";
var sux$2 = "Sumerian";
var swa$2 = "Swahili";
var swe$2 = "Swedish";
var syc$2 = "Syriac";
var syr$2 = "Syriac, Modern";
var tah$2 = "Tahitian";
var tai$2 = "Tai (Other)";
var tam$2 = "Tamil";
var tat$2 = "Tatar";
var tel$2 = "Telugu";
var tem$2 = "Temne";
var ter$2 = "Terena";
var tet$2 = "Tetum";
var tgk$2 = "Tajik";
var tgl$2 = "Tagalog";
var tha$2 = "Thai";
var tib$2 = "Tibetan";
var tig$2 = "Tigré";
var tir$2 = "Tigrinya";
var tiv$2 = "Tiv";
var tkl$2 = "Tokelauan";
var tlh$2 = "Klingon (Artificial language)";
var tli$2 = "Tlingit";
var tmh$2 = "Tamashek";
var tog$2 = "Tonga (Nyasa)";
var ton$2 = "Tongan";
var tpi$2 = "Tok Pisin";
var tsi$2 = "Tsimshian";
var tsn$2 = "Tswana";
var tso$2 = "Tsonga";
var tuk$2 = "Turkmen";
var tum$2 = "Tumbuka";
var tup$2 = "Tupi languages";
var tur$2 = "Turkish";
var tut$2 = "Altaic (Other)";
var tvl$2 = "Tuvaluan";
var twi$2 = "Twi";
var tyv$2 = "Tuvinian";
var udm$2 = "Udmurt";
var uga$2 = "Ugaritic";
var uig$2 = "Uighur";
var ukr$2 = "Ukrainian";
var umb$2 = "Umbundu";
var und$2 = "Undetermined";
var urd$2 = "Urdu";
var uzb$2 = "Uzbek";
var vai$2 = "Vai";
var ven$2 = "Venda";
var vie$2 = "Vietnamese";
var vol$2 = "Volapük";
var vot$2 = "Votic";
var wak$2 = "Wakashan languages";
var wal$2 = "Wolayta";
var war$2 = "Waray";
var was$2 = "Washoe";
var wel$2 = "Welsh";
var wen$2 = "Sorbian (Other)";
var wln$2 = "Walloon";
var wol$2 = "Wolof";
var xal$2 = "Oirat";
var xho$2 = "Xhosa";
var yao$2 = "Yao (Africa)";
var yap$2 = "Yapese";
var yid$2 = "Yiddish";
var yor$2 = "Yoruba";
var ypk$2 = "Yupik languages";
var zap$2 = "Zapotec";
var zbl$2 = "Blissymbolics";
var zen$2 = "Zenaga";
var zha$2 = "Zhuang";
var znd$2 = "Zande languages";
var zul$2 = "Zulu";
var zun$2 = "Zuni";
var zxx$2 = "No linguistic content";
var zza$2 = "Zaza";
var en$5 = {
aar: aar$2,
abk: abk$2,
ace: ace$2,
ach: ach$2,
ada: ada$2,
ady: ady$2,
afa: afa$2,
afh: afh$2,
afr: afr$2,
ain: ain$2,
aka: aka$2,
akk: akk$2,
alb: alb$2,
ale: ale$2,
alg: alg$2,
alt: alt$2,
amh: amh$2,
ang: ang$2,
anp: anp$2,
apa: apa$2,
ara: ara$2,
arc: arc$2,
arg: arg$2,
arm: arm$2,
arn: arn$2,
arp: arp$2,
art: art$2,
arw: arw$2,
asm: asm$2,
ast: ast$2,
ath: ath$2,
aus: aus$2,
ava: ava$2,
ave: ave$2,
awa: awa$2,
aym: aym$2,
aze: aze$2,
bad: bad$2,
bai: bai$2,
bak: bak$2,
bal: bal$2,
bam: bam$2,
ban: ban$2,
baq: baq$2,
bas: bas$2,
bat: bat$2,
bej: bej$2,
bel: bel$2,
bem: bem$2,
ben: ben$2,
ber: ber$2,
bho: bho$2,
bih: bih$2,
bik: bik$2,
bin: bin$2,
bis: bis$2,
bla: bla$2,
bnt: bnt$2,
bos: bos$2,
bra: bra$2,
bre: bre$2,
btk: btk$2,
bua: bua$2,
bug: bug$2,
bul: bul$2,
bur: bur$2,
byn: byn$2,
cad: cad$2,
cai: cai$2,
car: car$2,
cat: cat$2,
cau: cau$2,
ceb: ceb$2,
cel: cel$2,
cha: cha$2,
chb: chb$2,
che: che$2,
chg: chg$2,
chi: chi$2,
chk: chk$2,
chm: chm$2,
chn: chn$2,
cho: cho$2,
chp: chp$2,
chr: chr$2,
chu: chu$2,
chv: chv$2,
chy: chy$2,
cmc: cmc$2,
cnr: cnr$2,
cop: cop$2,
cor: cor$2,
cos: cos$2,
cpe: cpe$2,
cpf: cpf$2,
cpp: cpp$2,
cre: cre$2,
crh: crh$2,
crp: crp$2,
csb: csb$2,
cus: cus$2,
cze: cze$2,
dak: dak$2,
dan: dan$2,
dar: dar$2,
day: day$2,
del: del$2,
den: den$2,
dgr: dgr$2,
din: din$2,
div: div$2,
doi: doi$2,
dra: dra$2,
dsb: dsb$2,
dua: dua$2,
dum: dum$2,
dut: dut$2,
dyu: dyu$2,
dzo: dzo$2,
efi: efi$2,
egy: egy$2,
eka: eka$2,
elx: elx$2,
eng: eng$2,
enm: enm$2,
epo: epo$2,
est: est$2,
ewe: ewe$2,
ewo: ewo$2,
fan: fan$2,
fao: fao$2,
fat: fat$2,
fij: fij$2,
fil: fil$2,
fin: fin$2,
fiu: fiu$2,
fon: fon$2,
fre: fre$2,
frm: frm$2,
fro: fro$2,
frr: frr$2,
frs: frs$2,
fry: fry$2,
ful: ful$2,
fur: fur$2,
gaa: gaa$2,
gay: gay$2,
gba: gba$2,
gem: gem$2,
geo: geo$2,
ger: ger$2,
gez: gez$2,
gil: gil$2,
gla: gla$2,
gle: gle$2,
glg: glg$2,
glv: glv$2,
gmh: gmh$2,
goh: goh$2,
gon: gon$2,
gor: gor$2,
got: got$2,
grb: grb$2,
grc: grc$2,
gre: gre$2,
grn: grn$2,
gsw: gsw$2,
guj: guj$2,
gwi: gwi$2,
hai: hai$2,
hat: hat$2,
hau: hau$2,
haw: haw$2,
heb: heb$2,
her: her$2,
hil: hil$2,
him: him$2,
hin: hin$2,
hit: hit$2,
hmn: hmn$2,
hmo: hmo$2,
hrv: hrv$2,
hsb: hsb$2,
hun: hun$2,
hup: hup$2,
iba: iba$2,
ibo: ibo$2,
ice: ice$2,
ido: ido$2,
iii: iii$2,
ijo: ijo$2,
iku: iku$2,
ile: ile$2,
ilo: ilo$2,
ina: ina$2,
inc: inc$2,
ind: ind$2,
ine: ine$2,
inh: inh$2,
ipk: ipk$2,
ira: ira$2,
iro: iro$2,
ita: ita$2,
jav: jav$2,
jbo: jbo$2,
jpn: jpn$2,
jpr: jpr$2,
jrb: jrb$2,
kaa: kaa$2,
kab: kab$2,
kac: kac$2,
kal: kal$2,
kam: kam$2,
kan: kan$2,
kar: kar$2,
kas: kas$2,
kau: kau$2,
kaw: kaw$2,
kaz: kaz$2,
kbd: kbd$2,
kha: kha$2,
khi: khi$2,
khm: khm$2,
kho: kho$2,
kik: kik$2,
kin: kin$2,
kir: kir$2,
kmb: kmb$2,
kok: kok$2,
kom: kom$2,
kon: kon$2,
kor: kor$2,
kos: kos$2,
kpe: kpe$2,
krc: krc$2,
krl: krl$2,
kro: kro$2,
kru: kru$2,
kua: kua$2,
kum: kum$2,
kur: kur$2,
kut: kut$2,
lad: lad$2,
lah: lah$2,
lam: lam$2,
lao: lao$2,
lat: lat$2,
lav: lav$2,
lez: lez$2,
lim: lim$2,
lin: lin$2,
lit: lit$2,
lol: lol$2,
loz: loz$2,
ltz: ltz$2,
lua: lua$2,
lub: lub$2,
lug: lug$2,
lui: lui$2,
lun: lun$2,
luo: luo$2,
lus: lus$2,
mac: mac$2,
mad: mad$2,
mag: mag$2,
mah: mah$2,
mai: mai$2,
mak: mak$2,
mal: mal$2,
man: man$2,
mao: mao$2,
map: map$2,
mar: mar$2,
mas: mas$2,
may: may$2,
mdf: mdf$2,
mdr: mdr$2,
men: men$2,
mga: mga$2,
mic: mic$2,
min: min$2,
mis: mis$2,
mkh: mkh$2,
mlg: mlg$2,
mlt: mlt$2,
mnc: mnc$2,
mni: mni$2,
mno: mno$2,
moh: moh$2,
mon: mon$2,
mos: mos$2,
mul: mul$2,
mun: mun$2,
mus: mus$2,
mwl: mwl$2,
mwr: mwr$2,
myn: myn$2,
myv: myv$2,
nah: nah$2,
nai: nai$2,
nap: nap$2,
nau: nau$2,
nav: nav$2,
nbl: nbl$2,
nde: nde$2,
ndo: ndo$2,
nds: nds$2,
nep: nep$2,
"new": "Newari",
nia: nia$2,
nic: nic$2,
niu: niu$2,
nno: nno$2,
nob: nob$2,
nog: nog$2,
non: non$2,
nor: nor$2,
nqo: nqo$2,
nso: nso$2,
nub: nub$2,
nwc: nwc$2,
nya: nya$2,
nym: nym$2,
nyn: nyn$2,
nyo: nyo$2,
nzi: nzi$2,
oci: oci$2,
oji: oji$2,
ori: ori$2,
orm: orm$2,
osa: osa$2,
oss: oss$2,
ota: ota$2,
oto: oto$2,
paa: paa$2,
pag: pag$2,
pal: pal$2,
pam: pam$2,
pan: pan$2,
pap: pap$2,
pau: pau$2,
peo: peo$2,
per: per$2,
phi: phi$2,
phn: phn$2,
pli: pli$2,
pol: pol$2,
pon: pon$2,
por: por$2,
pra: pra$2,
pro: pro$2,
pus: pus$2,
que: que$2,
raj: raj$2,
rap: rap$2,
rar: rar$2,
roa: roa$2,
roh: roh$2,
rom: rom$2,
rum: rum$2,
run: run$2,
rup: rup$2,
rus: rus$2,
sad: sad$2,
sag: sag$2,
sah: sah$2,
sai: sai$2,
sal: sal$2,
sam: sam$2,
san: san$2,
sas: sas$2,
sat: sat$2,
scn: scn$2,
sco: sco$2,
sel: sel$2,
sem: sem$2,
sga: sga$2,
sgn: sgn$2,
shn: shn$2,
sid: sid$2,
sin: sin$2,
sio: sio$2,
sit: sit$2,
sla: sla$2,
slo: slo$2,
slv: slv$2,
sma: sma$2,
sme: sme$2,
smi: smi$2,
smj: smj$2,
smn: smn$2,
smo: smo$2,
sms: sms$2,
sna: sna$2,
snd: snd$2,
snk: snk$2,
sog: sog$2,
som: som$2,
son: son$2,
sot: sot$2,
spa: spa$2,
srd: srd$2,
srn: srn$2,
srp: srp$2,
srr: srr$2,
ssa: ssa$2,
ssw: ssw$2,
suk: suk$2,
sun: sun$2,
sus: sus$2,
sux: sux$2,
swa: swa$2,
swe: swe$2,
syc: syc$2,
syr: syr$2,
tah: tah$2,
tai: tai$2,
tam: tam$2,
tat: tat$2,
tel: tel$2,
tem: tem$2,
ter: ter$2,
tet: tet$2,
tgk: tgk$2,
tgl: tgl$2,
tha: tha$2,
tib: tib$2,
tig: tig$2,
tir: tir$2,
tiv: tiv$2,
tkl: tkl$2,
tlh: tlh$2,
tli: tli$2,
tmh: tmh$2,
tog: tog$2,
ton: ton$2,
tpi: tpi$2,
tsi: tsi$2,
tsn: tsn$2,
tso: tso$2,
tuk: tuk$2,
tum: tum$2,
tup: tup$2,
tur: tur$2,
tut: tut$2,
tvl: tvl$2,
twi: twi$2,
tyv: tyv$2,
udm: udm$2,
uga: uga$2,
uig: uig$2,
ukr: ukr$2,
umb: umb$2,
und: und$2,
urd: urd$2,
uzb: uzb$2,
vai: vai$2,
ven: ven$2,
vie: vie$2,
vol: vol$2,
vot: vot$2,
wak: wak$2,
wal: wal$2,
war: war$2,
was: was$2,
wel: wel$2,
wen: wen$2,
wln: wln$2,
wol: wol$2,
xal: xal$2,
xho: xho$2,
yao: yao$2,
yap: yap$2,
yid: yid$2,
yor: yor$2,
ypk: ypk$2,
zap: zap$2,
zbl: zbl$2,
zen: zen$2,
zha: zha$2,
znd: znd$2,
zul: zul$2,
zun: zun$2,
zxx: zxx$2,
zza: zza$2
};
var aar$1 = "afar";
var abk$1 = "abkhaze";
var ace$1 = "aceh";
var ach$1 = "acoli";
var ada$1 = "adangme";
var ady$1 = "adyguéen";
var afa$1 = "afro-asiatiques, autres langues";
var afh$1 = "afrihili";
var afr$1 = "afrikaans";
var ain$1 = "aïnou";
var aka$1 = "akan";
var akk$1 = "akkadien";
var alb$1 = "albanais";
var ale$1 = "aléoute";
var alg$1 = "algonquines, langues";
var alt$1 = "altaï du Sud";
var amh$1 = "amharique";
var ang$1 = "ancien anglais";
var anp$1 = "angika";
var apa$1 = "apaches, langues";
var ara$1 = "arabe";
var arc$1 = "araméen";
var arg$1 = "aragonais";
var arm$1 = "arménien";
var arn$1 = "mapuche";
var arp$1 = "arapaho";
var art$1 = "artificielles, autres langues";
var arw$1 = "arawak";
var asm$1 = "assamais";
var ast$1 = "asturien";
var ath$1 = "athapascanes, langues";
var aus$1 = "australiennes, langues";
var ava$1 = "avar";
var ave$1 = "avestique";
var awa$1 = "awadhi";
var aym$1 = "aymara";
var aze$1 = "azéri";
var bad$1 = "banda, langues";
var bai$1 = "bamilékés, langues";
var bak$1 = "bachkir";
var bal$1 = "baloutchi";
var bam$1 = "bambara";
var ban$1 = "balinais";
var baq$1 = "basque";
var bas$1 = "bassa";
var bat$1 = "baltes, langues";
var bej$1 = "bedja";
var bel$1 = "biélorusse";
var bem$1 = "bemba";
var ben$1 = "bengali";
var ber$1 = "berbères, autres langues";
var bho$1 = "bhodjpouri";
var bih$1 = "bihari";
var bik$1 = "bikol";
var bin$1 = "bini";
var bis$1 = "bichelamar";
var bla$1 = "siksika";
var bnt$1 = "bantoues, autres langues";
var bos$1 = "bosniaque";
var bra$1 = "braj";
var bre$1 = "breton";
var btk$1 = "batak, langues";
var bua$1 = "bouriate";
var bug$1 = "bugi";
var bul$1 = "bulgare";
var bur$1 = "birman";
var byn$1 = "blin";
var cad$1 = "caddo";
var cai$1 = "indiennes d'Amérique centrale, autres langues";
var car$1 = "caribe";
var cat$1 = "catalan";
var cau$1 = "caucasiennes, autres langues";
var ceb$1 = "cebuano";
var cel$1 = "celtique, autres langues";
var cha$1 = "chamorro";
var chb$1 = "chibcha";
var che$1 = "tchétchène";
var chg$1 = "tchaghataï";
var chi$1 = "chinois";
var chk$1 = "chuuk";
var chm$1 = "mari";
var chn$1 = "jargon chinook";
var cho$1 = "choctaw";
var chp$1 = "chipewyan";
var chr$1 = "cherokee";
var chu$1 = "slavon d’église";
var chv$1 = "tchouvache";
var chy$1 = "cheyenne";
var cmc$1 = "chamic";
var cnr$1 = "monténégrin";
var cop$1 = "copte";
var cor$1 = "cornique";
var cos$1 = "corse";
var cpe$1 = "créoles et pidgins anglais, autres";
var cpf$1 = "créoles et pidgins français, autres";
var cpp$1 = "créoles et pidgins portugais, autres";
var cre$1 = "cree";
var crh$1 = "turc de Crimée";
var crp$1 = "créoles et pidgins divers";
var csb$1 = "kachoube";
var cus$1 = "couchitiques, autres langues";
var cze$1 = "tchèque";
var dak$1 = "dakota";
var dan$1 = "danois";
var dar$1 = "dargwa";
var day$1 = "Land Dayak, langues";
var del$1 = "delaware";
var den$1 = "esclave";
var dgr$1 = "dogrib";
var din$1 = "dinka";
var div$1 = "maldivien";
var doi$1 = "dogri";
var dra$1 = "dravidiennes, langues";
var dsb$1 = "bas-sorabe";
var dua$1 = "douala";
var dum$1 = "moyen néerlandais";
var dut$1 = "néerlandais";
var dyu$1 = "dioula";
var dzo$1 = "dzongkha";
var efi$1 = "éfik";
var egy$1 = "égyptien ancien";
var eka$1 = "ékadjouk";
var elx$1 = "élamite";
var eng$1 = "anglais";
var enm$1 = "moyen anglais";
var epo$1 = "espéranto";
var est$1 = "estonien";
var ewe$1 = "éwé";
var ewo$1 = "éwondo";
var fan$1 = "fang";
var fao$1 = "féroïen";
var fat$1 = "fanti";
var fij$1 = "fidjien";
var fil$1 = "filipino";
var fin$1 = "finnois";
var fiu$1 = "finno-ougriennnes, autres langues";
var fon$1 = "fon";
var fre$1 = "français";
var frm$1 = "moyen français";
var fro$1 = "ancien français";
var frr$1 = "frison du Nord";
var frs$1 = "frison oriental";
var fry$1 = "frison occidental";
var ful$1 = "peul";
var fur$1 = "frioulan";
var gaa$1 = "ga";
var gay$1 = "gayo";
var gba$1 = "gbaya";
var gem$1 = "germaniques, autres langues";
var geo$1 = "géorgien";
var ger$1 = "allemand";
var gez$1 = "guèze";
var gil$1 = "gilbertin";
var gla$1 = "gaélique écossais";
var gle$1 = "irlandais";
var glg$1 = "galicien";
var glv$1 = "mannois";
var gmh$1 = "moyen haut-allemand";
var goh$1 = "ancien haut allemand";
var gon$1 = "gondi";
var gor$1 = "gorontalo";
var got$1 = "gotique";
var grb$1 = "grebo";
var grc$1 = "grec ancien (jusqu'à 1453)";
var gre$1 = "grec moderne (après 1453)";
var grn$1 = "guarani";
var gsw$1 = "suisse allemand";
var guj$1 = "goudjerati";
var gwi$1 = "gwichʼin";
var hai$1 = "haida";
var hat$1 = "créole haïtien";
var hau$1 = "haoussa";
var haw$1 = "hawaïen";
var heb$1 = "hébreu";
var her$1 = "héréro";
var hil$1 = "hiligaynon";
var him$1 = "himachali";
var hin$1 = "hindi";
var hit$1 = "hittite";
var hmn$1 = "hmong";
var hmo$1 = "hiri motu";
var hrv$1 = "croate";
var hsb$1 = "haut-sorabe";
var hun$1 = "hongrois";
var hup$1 = "hupa";
var iba$1 = "iban";
var ibo$1 = "igbo";
var ice$1 = "islandais";
var ido$1 = "ido";
var iii$1 = "yi du Sichuan";
var ijo$1 = "ijo, langues";
var iku$1 = "inuktitut";
var ile$1 = "interlingue";
var ilo$1 = "ilocano";
var ina$1 = "interlingua";
var inc$1 = "indo-aryennes, autres langues";
var ind$1 = "indonésien";
var ine$1 = "indo-européennes, autres langues";
var inh$1 = "ingouche";
var ipk$1 = "inupiaq";
var ira$1 = "iraniennes, autres langues";
var iro$1 = "iroquois, langues (famille)";
var ita$1 = "italien";
var jav$1 = "javanais";
var jbo$1 = "lojban";
var jpn$1 = "japonais";
var jpr$1 = "judéo-persan";
var jrb$1 = "judéo-arabe";
var kaa$1 = "karakalpak";
var kab$1 = "kabyle";
var kac$1 = "kachin";
var kal$1 = "groenlandais";
var kam$1 = "kamba";
var kan$1 = "kannada";
var kar$1 = "karen, langues";
var kas$1 = "cachemiri";
var kau$1 = "kanouri";
var kaw$1 = "kawi";
var kaz$1 = "kazakh";
var kbd$1 = "kabarde";
var kha$1 = "khasi";
var khi$1 = "khoisan, autres langues";
var khm$1 = "khmer";
var kho$1 = "khotanais";
var kik$1 = "kikuyu";
var kin$1 = "kinyarwanda";
var kir$1 = "kirghize";
var kmb$1 = "kimboundou";
var kok$1 = "konkani";
var kom$1 = "komi";
var kon$1 = "kikongo";
var kor$1 = "coréen";
var kos$1 = "kosraéen";
var kpe$1 = "kpellé";
var krc$1 = "karatchaï balkar";
var krl$1 = "carélien";
var kro$1 = "krou, langues";
var kru$1 = "kouroukh";
var kua$1 = "kuanyama";
var kum$1 = "koumyk";
var kur$1 = "kurde";
var kut$1 = "kutenai";
var lad$1 = "ladino";
var lah$1 = "lahnda";
var lam$1 = "lamba";
var lao$1 = "lao";
var lat$1 = "latin";
var lav$1 = "letton";
var lez$1 = "lezghien";
var lim$1 = "limbourgeois";
var lin$1 = "lingala";
var lit$1 = "lituanien";
var lol$1 = "mongo";
var loz$1 = "lozi";
var ltz$1 = "luxembourgeois";
var lua$1 = "luba-kasaï (ciluba)";
var lub$1 = "luba-katanga (kiluba)";
var lug$1 = "ganda";
var lui$1 = "luiseño";
var lun$1 = "lunda";
var luo$1 = "luo";
var lus$1 = "lushaï";
var mac$1 = "macédonien";
var mad$1 = "madurais";
var mag$1 = "magahi";
var mah$1 = "marshallais";
var mai$1 = "maïthili";
var mak$1 = "makassar";
var mal$1 = "malayalam";
var man$1 = "mandingue";
var mao$1 = "maori";
var map$1 = "malayo-polynésiennes, autres langues";
var mar$1 = "marathi";
var mas$1 = "maasaï";
var may$1 = "malais";
var mdf$1 = "mokcha";
var mdr$1 = "mandar";
var men$1 = "mendé";
var mga$1 = "moyen irlandais";
var mic$1 = "micmac";
var min$1 = "minangkabau";
var mis$1 = "diverses, langues";
var mkh$1 = "môn-khmer, langues";
var mlg$1 = "malgache";
var mlt$1 = "maltais";
var mnc$1 = "mandchou";
var mni$1 = "manipuri";
var mno$1 = "manobo, langues";
var moh$1 = "mohawk";
var mon$1 = "mongol";
var mos$1 = "moré";
var mul$1 = "multilingue";
var mun$1 = "mounda, langues";
var mus$1 = "muskogee";
var mwl$1 = "mirandais";
var mwr$1 = "marwarî";
var myn$1 = "maya, langues";
var myv$1 = "erzya";
var nah$1 = "nahuatl, langues";
var nai$1 = "indiennes d'Amérique du Nord, autres langues";
var nap$1 = "napolitain";
var nau$1 = "nauruan";
var nav$1 = "navajo";
var nbl$1 = "ndébélé du Sud";
var nde$1 = "ndébélé du Nord";
var ndo$1 = "ndonga";
var nds$1 = "bas-allemand";
var nep$1 = "népalais";
var nia$1 = "niha";
var nic$1 = "nigéro-congolaises,langues";
var niu$1 = "niuéen";
var nno$1 = "norvégien nynorsk";
var nob$1 = "norvégien bokmål";
var nog$1 = "nogaï";
var non$1 = "vieux norrois";
var nor$1 = "norvégien";
var nqo$1 = "n’ko";
var nso$1 = "sotho du Nord";
var nub$1 = "nubiennes, langues";
var nwc$1 = "newarî classique";
var nya$1 = "chewa";
var nym$1 = "nyamwezi";
var nyn$1 = "nyankolé";
var nyo$1 = "nyoro";
var nzi$1 = "nzema";
var oci$1 = "occitan";
var oji$1 = "ojibwa";
var ori$1 = "odia";
var orm$1 = "oromo";
var osa$1 = "osage";
var oss$1 = "ossète";
var ota$1 = "turc ottoman";
var oto$1 = "otomi, langues";
var paa$1 = "papoues, autres langues";
var pag$1 = "pangasinan";
var pal$1 = "pahlavi";
var pam$1 = "pampangan";
var pan$1 = "pendjabi";
var pap$1 = "papiamento";
var pau$1 = "palau";
var peo$1 = "persan ancien";
var per$1 = "persan";
var phi$1 = "philippin, autres langues";
var phn$1 = "phénicien";
var pli$1 = "pali";
var pol$1 = "polonais";
var pon$1 = "pohnpei";
var por$1 = "portugais";
var pra$1 = "prâkrit";
var pro$1 = "provençal ancien";
var pus$1 = "pachto";
var que$1 = "quechua";
var raj$1 = "rajasthani";
var rap$1 = "rapanui";
var rar$1 = "rarotongien";
var roa$1 = "romanes, langues";
var roh$1 = "romanche";
var rom$1 = "romani";
var rum$1 = "roumain";
var run$1 = "roundi";
var rup$1 = "aroumain";
var rus$1 = "russe";
var sad$1 = "sandawe";
var sag$1 = "sango";
var sah$1 = "iakoute";
var sai$1 = "indiennes d'Amérique du Sud, autres langues";
var sal$1 = "salish, langues";
var sam$1 = "araméen samaritain";
var san$1 = "sanskrit";
var sas$1 = "sasak";
var sat$1 = "santali";
var scn$1 = "sicilien";
var sco$1 = "écossais";
var sel$1 = "selkoupe";
var sem$1 = "sémitiques, langues";
var sga$1 = "ancien irlandais";
var sgn$1 = "langue des signes";
var shn$1 = "shan";
var sid$1 = "sidamo";
var sin$1 = "cingalais";
var sio$1 = "sioux, langues";
var sit$1 = "sino-tibétaines, autres langues";
var sla$1 = "slaves, langues";
var slo$1 = "slovaque";
var slv$1 = "slovène";
var sma$1 = "same du Sud";
var sme$1 = "same du Nord";
var smi$1 = "sâmes, langues";
var smj$1 = "same de Lule";
var smn$1 = "same d’Inari";
var smo$1 = "samoan";
var sms$1 = "same skolt";
var sna$1 = "shona";
var snd$1 = "sindhi";
var snk$1 = "soninké";
var sog$1 = "sogdien";
var som$1 = "somali";
var son$1 = "songhai";
var sot$1 = "sotho du Sud";
var spa$1 = "espagnol";
var srd$1 = "sarde";
var srn$1 = "sranan tongo";
var srp$1 = "serbe";
var srr$1 = "sérère";
var ssa$1 = "nilo-sahariennes, langues";
var ssw$1 = "swati";
var suk$1 = "soukouma";
var sun$1 = "soundanais";
var sus$1 = "soussou";
var sux$1 = "sumérien";
var swa$1 = "swahili";
var swe$1 = "suédois";
var syc$1 = "syriaque classique";
var syr$1 = "syriaque";
var tah$1 = "tahitien";
var tai$1 = "tai, langues";
var tam$1 = "tamoul";
var tat$1 = "tatar";
var tel$1 = "télougou";
var tem$1 = "timné";
var ter$1 = "tereno";
var tet$1 = "tétoum";
var tgk$1 = "tadjik";
var tgl$1 = "tagalog";
var tha$1 = "thaï";
var tib$1 = "tibétain";
var tig$1 = "tigré";
var tir$1 = "tigrigna";
var tiv$1 = "tiv";
var tkl$1 = "tokelau";
var tlh$1 = "klingon";
var tli$1 = "tlingit";
var tmh$1 = "tamacheq";
var tog$1 = "tonga nyasa";
var ton$1 = "tongien";
var tpi$1 = "tok pisin";
var tsi$1 = "tsimshian";
var tsn$1 = "tswana";
var tso$1 = "tsonga";
var tuk$1 = "turkmène";
var tum$1 = "tumbuka";
var tup$1 = "tupi, langues";
var tur$1 = "turc";
var tut$1 = "altaïques, langues";
var tvl$1 = "tuvalu";
var twi$1 = "twi";
var tyv$1 = "touvain";
var udm$1 = "oudmourte";
var uga$1 = "ougaritique";
var uig$1 = "ouïghour";
var ukr$1 = "ukrainien";
var umb$1 = "umbundu";
var und$1 = "langue indéterminée";
var urd$1 = "ourdou";
var uzb$1 = "ouzbek";
var vai$1 = "vaï";
var ven$1 = "venda";
var vie$1 = "vietnamien";
var vol$1 = "volapük";
var vot$1 = "vote";
var wak$1 = "wakashennes, langues";
var wal$1 = "walamo";
var war$1 = "waray";
var was$1 = "washo";
var wel$1 = "gallois";
var wen$1 = "sorabes, langues";
var wln$1 = "wallon";
var wol$1 = "wolof";
var xal$1 = "kalmouk";
var xho$1 = "xhosa";
var yao$1 = "yao";
var yap$1 = "yapois";
var yid$1 = "yiddish";
var yor$1 = "yoruba";
var ypk$1 = "yupik, langues";
var zap$1 = "zapotèque";
var zbl$1 = "symboles Bliss";
var zen$1 = "zenaga";
var zha$1 = "zhuang";
var znd$1 = "zandé, langues";
var zul$1 = "zoulou";
var zun$1 = "zuñi";
var zxx$1 = "sans contenu linguistique";
var zza$1 = "zazaki";
var fr$5 = {
aar: aar$1,
abk: abk$1,
ace: ace$1,
ach: ach$1,
ada: ada$1,
ady: ady$1,
afa: afa$1,
afh: afh$1,
afr: afr$1,
ain: ain$1,
aka: aka$1,
akk: akk$1,
alb: alb$1,
ale: ale$1,
alg: alg$1,
alt: alt$1,
amh: amh$1,
ang: ang$1,
anp: anp$1,
apa: apa$1,
ara: ara$1,
arc: arc$1,
arg: arg$1,
arm: arm$1,
arn: arn$1,
arp: arp$1,
art: art$1,
arw: arw$1,
asm: asm$1,
ast: ast$1,
ath: ath$1,
aus: aus$1,
ava: ava$1,
ave: ave$1,
awa: awa$1,
aym: aym$1,
aze: aze$1,
bad: bad$1,
bai: bai$1,
bak: bak$1,
bal: bal$1,
bam: bam$1,
ban: ban$1,
baq: baq$1,
bas: bas$1,
bat: bat$1,
bej: bej$1,
bel: bel$1,
bem: bem$1,
ben: ben$1,
ber: ber$1,
bho: bho$1,
bih: bih$1,
bik: bik$1,
bin: bin$1,
bis: bis$1,
bla: bla$1,
bnt: bnt$1,
bos: bos$1,
bra: bra$1,
bre: bre$1,
btk: btk$1,
bua: bua$1,
bug: bug$1,
bul: bul$1,
bur: bur$1,
byn: byn$1,
cad: cad$1,
cai: cai$1,
car: car$1,
cat: cat$1,
cau: cau$1,
ceb: ceb$1,
cel: cel$1,
cha: cha$1,
chb: chb$1,
che: che$1,
chg: chg$1,
chi: chi$1,
chk: chk$1,
chm: chm$1,
chn: chn$1,
cho: cho$1,
chp: chp$1,
chr: chr$1,
chu: chu$1,
chv: chv$1,
chy: chy$1,
cmc: cmc$1,
cnr: cnr$1,
cop: cop$1,
cor: cor$1,
cos: cos$1,
cpe: cpe$1,
cpf: cpf$1,
cpp: cpp$1,
cre: cre$1,
crh: crh$1,
crp: crp$1,
csb: csb$1,
cus: cus$1,
cze: cze$1,
dak: dak$1,
dan: dan$1,
dar: dar$1,
day: day$1,
del: del$1,
den: den$1,
dgr: dgr$1,
din: din$1,
div: div$1,
doi: doi$1,
dra: dra$1,
dsb: dsb$1,
dua: dua$1,
dum: dum$1,
dut: dut$1,
dyu: dyu$1,
dzo: dzo$1,
efi: efi$1,
egy: egy$1,
eka: eka$1,
elx: elx$1,
eng: eng$1,
enm: enm$1,
epo: epo$1,
est: est$1,
ewe: ewe$1,
ewo: ewo$1,
fan: fan$1,
fao: fao$1,
fat: fat$1,
fij: fij$1,
fil: fil$1,
fin: fin$1,
fiu: fiu$1,
fon: fon$1,
fre: fre$1,
frm: frm$1,
fro: fro$1,
frr: frr$1,
frs: frs$1,
fry: fry$1,
ful: ful$1,
fur: fur$1,
gaa: gaa$1,
gay: gay$1,
gba: gba$1,
gem: gem$1,
geo: geo$1,
ger: ger$1,
gez: gez$1,
gil: gil$1,
gla: gla$1,
gle: gle$1,
glg: glg$1,
glv: glv$1,
gmh: gmh$1,
goh: goh$1,
gon: gon$1,
gor: gor$1,
got: got$1,
grb: grb$1,
grc: grc$1,
gre: gre$1,
grn: grn$1,
gsw: gsw$1,
guj: guj$1,
gwi: gwi$1,
hai: hai$1,
hat: hat$1,
hau: hau$1,
haw: haw$1,
heb: heb$1,
her: her$1,
hil: hil$1,
him: him$1,
hin: hin$1,
hit: hit$1,
hmn: hmn$1,
hmo: hmo$1,
hrv: hrv$1,
hsb: hsb$1,
hun: hun$1,
hup: hup$1,
iba: iba$1,
ibo: ibo$1,
ice: ice$1,
ido: ido$1,
iii: iii$1,
ijo: ijo$1,
iku: iku$1,
ile: ile$1,
ilo: ilo$1,
ina: ina$1,
inc: inc$1,
ind: ind$1,
ine: ine$1,
inh: inh$1,
ipk: ipk$1,
ira: ira$1,
iro: iro$1,
ita: ita$1,
jav: jav$1,
jbo: jbo$1,
jpn: jpn$1,
jpr: jpr$1,
jrb: jrb$1,
kaa: kaa$1,
kab: kab$1,
kac: kac$1,
kal: kal$1,
kam: kam$1,
kan: kan$1,
kar: kar$1,
kas: kas$1,
kau: kau$1,
kaw: kaw$1,
kaz: kaz$1,
kbd: kbd$1,
kha: kha$1,
khi: khi$1,
khm: khm$1,
kho: kho$1,
kik: kik$1,
kin: kin$1,
kir: kir$1,
kmb: kmb$1,
kok: kok$1,
kom: kom$1,
kon: kon$1,
kor: kor$1,
kos: kos$1,
kpe: kpe$1,
krc: krc$1,
krl: krl$1,
kro: kro$1,
kru: kru$1,
kua: kua$1,
kum: kum$1,
kur: kur$1,
kut: kut$1,
lad: lad$1,
lah: lah$1,
lam: lam$1,
lao: lao$1,
lat: lat$1,
lav: lav$1,
lez: lez$1,
lim: lim$1,
lin: lin$1,
lit: lit$1,
lol: lol$1,
loz: loz$1,
ltz: ltz$1,
lua: lua$1,
lub: lub$1,
lug: lug$1,
lui: lui$1,
lun: lun$1,
luo: luo$1,
lus: lus$1,
mac: mac$1,
mad: mad$1,
mag: mag$1,
mah: mah$1,
mai: mai$1,
mak: mak$1,
mal: mal$1,
man: man$1,
mao: mao$1,
map: map$1,
mar: mar$1,
mas: mas$1,
may: may$1,
mdf: mdf$1,
mdr: mdr$1,
men: men$1,
mga: mga$1,
mic: mic$1,
min: min$1,
mis: mis$1,
mkh: mkh$1,
mlg: mlg$1,
mlt: mlt$1,
mnc: mnc$1,
mni: mni$1,
mno: mno$1,
moh: moh$1,
mon: mon$1,
mos: mos$1,
mul: mul$1,
mun: mun$1,
mus: mus$1,
mwl: mwl$1,
mwr: mwr$1,
myn: myn$1,
myv: myv$1,
nah: nah$1,
nai: nai$1,
nap: nap$1,
nau: nau$1,
nav: nav$1,
nbl: nbl$1,
nde: nde$1,
ndo: ndo$1,
nds: nds$1,
nep: nep$1,
"new": "newari",
nia: nia$1,
nic: nic$1,
niu: niu$1,
nno: nno$1,
nob: nob$1,
nog: nog$1,
non: non$1,
nor: nor$1,
nqo: nqo$1,
nso: nso$1,
nub: nub$1,
nwc: nwc$1,
nya: nya$1,
nym: nym$1,
nyn: nyn$1,
nyo: nyo$1,
nzi: nzi$1,
oci: oci$1,
oji: oji$1,
ori: ori$1,
orm: orm$1,
osa: osa$1,
oss: oss$1,
ota: ota$1,
oto: oto$1,
paa: paa$1,
pag: pag$1,
pal: pal$1,
pam: pam$1,
pan: pan$1,
pap: pap$1,
pau: pau$1,
peo: peo$1,
per: per$1,
phi: phi$1,
phn: phn$1,
pli: pli$1,
pol: pol$1,
pon: pon$1,
por: por$1,
pra: pra$1,
pro: pro$1,
pus: pus$1,
que: que$1,
raj: raj$1,
rap: rap$1,
rar: rar$1,
roa: roa$1,
roh: roh$1,
rom: rom$1,
rum: rum$1,
run: run$1,
rup: rup$1,
rus: rus$1,
sad: sad$1,
sag: sag$1,
sah: sah$1,
sai: sai$1,
sal: sal$1,
sam: sam$1,
san: san$1,
sas: sas$1,
sat: sat$1,
scn: scn$1,
sco: sco$1,
sel: sel$1,
sem: sem$1,
sga: sga$1,
sgn: sgn$1,
shn: shn$1,
sid: sid$1,
sin: sin$1,
sio: sio$1,
sit: sit$1,
sla: sla$1,
slo: slo$1,
slv: slv$1,
sma: sma$1,
sme: sme$1,
smi: smi$1,
smj: smj$1,
smn: smn$1,
smo: smo$1,
sms: sms$1,
sna: sna$1,
snd: snd$1,
snk: snk$1,
sog: sog$1,
som: som$1,
son: son$1,
sot: sot$1,
spa: spa$1,
srd: srd$1,
srn: srn$1,
srp: srp$1,
srr: srr$1,
ssa: ssa$1,
ssw: ssw$1,
suk: suk$1,
sun: sun$1,
sus: sus$1,
sux: sux$1,
swa: swa$1,
swe: swe$1,
syc: syc$1,
syr: syr$1,
tah: tah$1,
tai: tai$1,
tam: tam$1,
tat: tat$1,
tel: tel$1,
tem: tem$1,
ter: ter$1,
tet: tet$1,
tgk: tgk$1,
tgl: tgl$1,
tha: tha$1,
tib: tib$1,
tig: tig$1,
tir: tir$1,
tiv: tiv$1,
tkl: tkl$1,
tlh: tlh$1,
tli: tli$1,
tmh: tmh$1,
tog: tog$1,
ton: ton$1,
tpi: tpi$1,
tsi: tsi$1,
tsn: tsn$1,
tso: tso$1,
tuk: tuk$1,
tum: tum$1,
tup: tup$1,
tur: tur$1,
tut: tut$1,
tvl: tvl$1,
twi: twi$1,
tyv: tyv$1,
udm: udm$1,
uga: uga$1,
uig: uig$1,
ukr: ukr$1,
umb: umb$1,
und: und$1,
urd: urd$1,
uzb: uzb$1,
vai: vai$1,
ven: ven$1,
vie: vie$1,
vol: vol$1,
vot: vot$1,
wak: wak$1,
wal: wal$1,
war: war$1,
was: was$1,
wel: wel$1,
wen: wen$1,
wln: wln$1,
wol: wol$1,
xal: xal$1,
xho: xho$1,
yao: yao$1,
yap: yap$1,
yid: yid$1,
yor: yor$1,
ypk: ypk$1,
zap: zap$1,
zbl: zbl$1,
zen: zen$1,
zha: zha$1,
znd: znd$1,
zul: zul$1,
zun: zun$1,
zxx: zxx$1,
zza: zza$1
};
var aar = "afar";
var abk = "abcaso";
var ace = "accinese";
var ach = "acioli";
var ada = "adangme";
var ady = "adyghe";
var afa = "afroasiatiche (altre)";
var afh = "afrihili";
var afr = "afrikaans";
var ain = "ainu";
var aka = "akan";
var akk = "accado";
var alb = "albanese";
var ale = "aleuto";
var alg = "lingue algonchine";
var alt = "altai meridionale";
var amh = "amarico";
var ang = "inglese antico";
var anp = "angika";
var apa = "lingue apache";
var ara = "arabo";
var arc = "aramaico";
var arg = "aragonese";
var arm = "armeno";
var arn = "mapudungun";
var arp = "arapaho";
var art = "lingua artificiale (altre)";
var arw = "aruaco";
var asm = "assamese";
var ast = "asturiano";
var ath = "lingue athabaska";
var aus = "lingue australiane aborigene";
var ava = "avaro";
var ave = "avestan";
var awa = "awadhi";
var aym = "aymara";
var aze = "azerbaigiano";
var bad = "banda";
var bai = "lingue bamileke";
var bak = "baschiro";
var bal = "beluci";
var bam = "bambara";
var ban = "balinese";
var baq = "basco";
var bas = "basa";
var bat = "lingue baltiche (altre)";
var bej = "begia";
var bel = "bielorusso";
var bem = "wemba";
var ben = "bengalese";
var ber = "lingue berbere";
var bho = "bhojpuri";
var bih = "bihari";
var bik = "bicol";
var bin = "bini";
var bis = "bislama";
var bla = "siksika";
var bnt = "bantu (altre)";
var bos = "bosniaco";
var bra = "braj";
var bre = "bretone";
var btk = "batak (indonesia)";
var bua = "buriat";
var bug = "bugi";
var bul = "bulgaro";
var bur = "birmano";
var byn = "blin";
var cad = "caddo";
var cai = "indiane centro-americane (altre)";
var car = "caribico";
var cat = "catalano";
var cau = "lingue caucasiche (altre)";
var ceb = "cebuano";
var cel = "lingue celtiche (altre)";
var cha = "chamorro";
var chb = "chibcha";
var che = "ceceno";
var chg = "ciagataico";
var chi = "cinese";
var chk = "chuukese";
var chm = "mari";
var chn = "gergo chinook";
var cho = "choctaw";
var chp = "chipewyan";
var chr = "cherokee";
var chu = "slavo della Chiesa";
var chv = "ciuvascio";
var chy = "cheyenne";
var cmc = "chamic";
var cnr = "montenegrino";
var cop = "copto";
var cor = "cornico";
var cos = "corso";
var cpe = "creoli e pidgin basati sull'inglese (altre)";
var cpf = "creoli e pidgin basati sul francese (altre)";
var cpp = "creoli e pidgin basati sul portoghese (altre)";
var cre = "cree";
var crh = "turco crimeo";
var crp = "creoli e pidgin (altre)";
var csb = "kashubian";
var cus = "lingue cushitiche (altre)";
var cze = "ceco";
var dak = "dakota";
var dan = "danese";
var dar = "dargwa";
var day = "dayak";
var del = "delaware";
var den = "slave";
var dgr = "dogrib";
var din = "dinca";
var div = "divehi";
var doi = "dogri";
var dra = "dravidiche (altre)";
var dsb = "basso sorabo";
var dua = "duala";
var dum = "olandese medio";
var dut = "olandese; fiammingo";
var dyu = "diula";
var dzo = "dzongkha";
var efi = "efik";
var egy = "egiziano antico";
var eka = "ekajuka";
var elx = "elamitico";
var eng = "inglese";
var enm = "inglese medio";
var epo = "esperanto";
var est = "estone";
var ewe = "ewe";
var ewo = "ewondo";
var fan = "fang";
var fao = "faroese";
var fat = "fanti";
var fij = "figiano";
var fil = "filippino";
var fin = "finlandese";
var fiu = "ugrofinniche (altre)";
var fon = "fon";
var fre = "francese";
var frm = "francese medio";
var fro = "francese antico";
var frr = "frisone settentrionale";
var frs = "frisone orientale";
var fry = "frisone occidentale";
var ful = "fulah";
var fur = "friulano";
var gaa = "ga";
var gay = "gayo";
var gba = "gbaya";
var gem = "lingue germaniche (altre)";
var geo = "georgiano";
var ger = "tedesco";
var gez = "geez";
var gil = "gilbertese";
var gla = "gaelico scozzese";
var gle = "irlandese";
var glg = "galiziano";
var glv = "mannese";
var gmh = "tedesco medio alto";
var goh = "tedesco antico alto";
var gon = "gondi";
var gor = "gorontalo";
var got = "gotico";
var grb = "grebo";
var grc = "greco antico";
var gre = "greco moderno (dal 1453)";
var grn = "guaraní";
var gsw = "tedesco svizzero";
var guj = "gujarati";
var gwi = "gwichʼin";
var hai = "haida";
var hat = "haitiano";
var hau = "hausa";
var haw = "hawaiano";
var heb = "ebraico";
var her = "herero";
var hil = "ilongo";
var him = "himachali";
var hin = "hindi";
var hit = "hittite";
var hmn = "hmong";
var hmo = "hiri motu";
var hrv = "croato";
var hsb = "alto sorabo";
var hun = "ungherese";
var hup = "hupa";
var iba = "iban";
var ibo = "igbo";
var ice = "islandese";
var ido = "ido";
var iii = "sichuan yi";
var ijo = "ijo";
var iku = "inuktitut";
var ile = "interlingue";
var ilo = "ilocano";
var ina = "interlingua";
var inc = "lingue indoarie (altre)";
var ind = "indonesiano";
var ine = "lingue indoeuropee (altre)";
var inh = "ingush";
var ipk = "inupiak";
var ira = "lingue iraniche (altre)";
var iro = "lingue irochesi";
var ita = "italiano";
var jav = "giavanese";
var jbo = "lojban";
var jpn = "giapponese";
var jpr = "giudeo persiano";
var jrb = "giudeo arabo";
var kaa = "kara-kalpak";
var kab = "cabilo";
var kac = "kachin";
var kal = "groenlandese";
var kam = "kamba";
var kan = "kannada";
var kar = "karen";
var kas = "kashmiri";
var kau = "kanuri";
var kaw = "kawi";
var kaz = "kazako";
var kbd = "cabardino";
var kha = "khasi";
var khi = "khoisan (altre)";
var khm = "khmer";
var kho = "khotanese";
var kik = "kikuyu";
var kin = "kinyarwanda";
var kir = "kirghiso";
var kmb = "kimbundu";
var kok = "konkani";
var kom = "komi";
var kon = "kongo";
var kor = "coreano";
var kos = "kosraean";
var kpe = "kpelle";
var krc = "karachay-Balkar";
var krl = "careliano";
var kro = "kru";
var kru = "kurukh";
var kua = "kuanyama";
var kum = "kumyk";
var kur = "curdo";
var kut = "kutenai";
var lad = "giudeo-spagnolo";
var lah = "lahnda";
var lam = "lamba";
var lao = "lao";
var lat = "latino";
var lav = "lettone";
var lez = "lesgo";
var lim = "limburghese";
var lin = "lingala";
var lit = "lituano";
var lol = "lolo bantu";
var loz = "lozi";
var ltz = "lussemburghese";
var lua = "luba-lulua";
var lub = "luba-katanga";
var lug = "ganda";
var lui = "luiseno";
var lun = "lunda";
var luo = "luo";
var lus = "lushai";
var mac = "macedone";
var mad = "madurese";
var mag = "magahi";
var mah = "marshallese";
var mai = "maithili";
var mak = "makasar";
var mal = "malayalam";
var man = "mandingo";
var mao = "maori";
var map = "austronesiano (altre)";
var mar = "marathi";
var mas = "masai";
var may = "malay";
var mdf = "moksha";
var mdr = "mandar";
var men = "mende";
var mga = "irlandese medio";
var mic = "micmac";
var min = "menangkabau";
var mis = "ainu";
var mkh = "mon-khmer (altre)";
var mlg = "malgascio";
var mlt = "maltese";
var mnc = "manchu";
var mni = "manipuri";
var mno = "lingue manobo";
var moh = "mohawk";
var mon = "mongolo";
var mos = "mossi";
var mul = "multilingua";
var mun = "lingue munda";
var mus = "creek";
var mwl = "mirandese";
var mwr = "marwari";
var myn = "maya";
var myv = "erzya";
var nah = "nahuatl";
var nai = "indiano nordamericano (altre)";
var nap = "napoletano";
var nau = "nauru";
var nav = "navajo";
var nbl = "ndebele del sud";
var nde = "ndebele del nord";
var ndo = "ndonga";
var nds = "basso tedesco";
var nep = "nepalese";
var nia = "nias";
var nic = "niger-kordofaniane (altre)";
var niu = "niue";
var nno = "norvegese nynorsk";
var nob = "norvegese bokmål";
var nog = "nogai";
var non = "norse antico";
var nor = "norvegese";
var nqo = "n’ko";
var nso = "sotho del nord";
var nub = "lingue nubiane";
var nwc = "newari classico";
var nya = "nyanja";
var nym = "nyamwezi";
var nyn = "nyankole";
var nyo = "nyoro";
var nzi = "nzima";
var oci = "occitano";
var oji = "ojibwa";
var ori = "odia";
var orm = "oromo";
var osa = "osage";
var oss = "ossetico";
var ota = "turco ottomano";
var oto = "lingue otomiane";
var paa = "papuasiche";
var pag = "pangasinan";
var pal = "pahlavi";
var pam = "pampanga";
var pan = "punjabi";
var pap = "papiamento";
var pau = "palau";
var peo = "persiano antico";
var per = "persiano";
var phi = "filippine (altre)";
var phn = "fenicio";
var pli = "pali";
var pol = "polacco";
var pon = "ponape";
var por = "portoghese";
var pra = "lingue pracrite";
var pro = "provenzale antico";
var pus = "pashto";
var que = "quechua";
var raj = "rajasthani";
var rap = "rapanui";
var rar = "rarotonga";
var roa = "lingue romanze (altre)";
var roh = "romancio";
var rom = "romani";
var rum = "romeno";
var run = "rundi";
var rup = "arumeno";
var rus = "russo";
var sad = "sandawe";
var sag = "sango";
var sah = "yakut";
var sai = "lingue indiane sudamericane (altre)";
var sal = "lingue salish";
var sam = "aramaico samaritano";
var san = "sanscrito";
var sas = "sasak";
var sat = "santali";
var scn = "siciliano";
var sco = "scozzese";
var sel = "selkup";
var sem = "lingue semitiche (altre)";
var sga = "irlandese antico";
var sgn = "lingua dei segni";
var shn = "shan";
var sid = "sidamo";
var sin = "singalese";
var sio = "lingue sioux";
var sit = "sinotibetane (altre)";
var sla = "lingue slave (altre)";
var slo = "slovacco";
var slv = "sloveno";
var sma = "sami del sud";
var sme = "sami del nord";
var smi = "lingue sami (altre)";
var smj = "sami di Lule";
var smn = "sami di Inari";
var smo = "samoano";
var sms = "sami skolt";
var sna = "shona";
var snd = "sindhi";
var snk = "soninke";
var sog = "sogdiano";
var som = "somalo";
var son = "songhai";
var sot = "sotho del sud";
var spa = "spagnolo";
var srd = "sardo";
var srn = "sranan tongo";
var srp = "serbo";
var srr = "serer";
var ssa = "lingue nilo-sahariane (altre)";
var ssw = "swati";
var suk = "sukuma";
var sun = "sundanese";
var sus = "susu";
var sux = "sumero";
var swa = "swahili";
var swe = "svedese";
var syc = "siriaco classico";
var syr = "siriaco";
var tah = "taitiano";
var tai = "thailandese (altre)";
var tam = "tamil";
var tat = "tataro";
var tel = "telugu";
var tem = "temne";
var ter = "tereno";
var tet = "tetum";
var tgk = "tagico";
var tgl = "tagalog";
var tha = "thai";
var tib = "tibetano";
var tig = "tigre";
var tir = "tigrino";
var tiv = "tiv";
var tkl = "tokelau";
var tlh = "klingon";
var tli = "tlingit";
var tmh = "tamashek";
var tog = "nyasa del Tonga";
var ton = "tongano";
var tpi = "tok pisin";
var tsi = "tsimshian";
var tsn = "tswana";
var tso = "tsonga";
var tuk = "turcomanno";
var tum = "tumbuka";
var tup = "lingue tupi";
var tur = "turco";
var tut = "altaiche (altre)";
var tvl = "tuvalu";
var twi = "ci";
var tyv = "tuvinian";
var udm = "udmurt";
var uga = "ugaritico";
var uig = "uiguro";
var ukr = "ucraino";
var umb = "mbundu";
var und = "lingua imprecisata";
var urd = "urdu";
var uzb = "uzbeco";
var vai = "vai";
var ven = "venda";
var vie = "vietnamita";
var vol = "volapük";
var vot = "voto";
var wak = "lingue wakash";
var wal = "walamo";
var war = "waray";
var was = "washo";
var wel = "gallese";
var wen = "lingue lusaziane";
var wln = "vallone";
var wol = "wolof";
var xal = "kalmyk";
var xho = "xhosa";
var yao = "yao (bantu)";
var yap = "yapese";
var yid = "yiddish";
var yor = "yoruba";
var ypk = "lingue yupik";
var zap = "zapotec";
var zbl = "blissymbol";
var zen = "zenaga";
var zha = "zhuang";
var znd = "zande";
var zul = "zulu";
var zun = "zuni";
var zxx = "nessun contenuto linguistico";
var zza = "zaza";
var it$5 = {
aar: aar,
abk: abk,
ace: ace,
ach: ach,
ada: ada,
ady: ady,
afa: afa,
afh: afh,
afr: afr,
ain: ain,
aka: aka,
akk: akk,
alb: alb,
ale: ale,
alg: alg,
alt: alt,
amh: amh,
ang: ang,
anp: anp,
apa: apa,
ara: ara,
arc: arc,
arg: arg,
arm: arm,
arn: arn,
arp: arp,
art: art,
arw: arw,
asm: asm,
ast: ast,
ath: ath,
aus: aus,
ava: ava,
ave: ave,
awa: awa,
aym: aym,
aze: aze,
bad: bad,
bai: bai,
bak: bak,
bal: bal,
bam: bam,
ban: ban,
baq: baq,
bas: bas,
bat: bat,
bej: bej,
bel: bel,
bem: bem,
ben: ben,
ber: ber,
bho: bho,
bih: bih,
bik: bik,
bin: bin,
bis: bis,
bla: bla,
bnt: bnt,
bos: bos,
bra: bra,
bre: bre,
btk: btk,
bua: bua,
bug: bug,
bul: bul,
bur: bur,
byn: byn,
cad: cad,
cai: cai,
car: car,
cat: cat,
cau: cau,
ceb: ceb,
cel: cel,
cha: cha,
chb: chb,
che: che,
chg: chg,
chi: chi,
chk: chk,
chm: chm,
chn: chn,
cho: cho,
chp: chp,
chr: chr,
chu: chu,
chv: chv,
chy: chy,
cmc: cmc,
cnr: cnr,
cop: cop,
cor: cor,
cos: cos,
cpe: cpe,
cpf: cpf,
cpp: cpp,
cre: cre,
crh: crh,
crp: crp,
csb: csb,
cus: cus,
cze: cze,
dak: dak,
dan: dan,
dar: dar,
day: day,
del: del,
den: den,
dgr: dgr,
din: din,
div: div,
doi: doi,
dra: dra,
dsb: dsb,
dua: dua,
dum: dum,
dut: dut,
dyu: dyu,
dzo: dzo,
efi: efi,
egy: egy,
eka: eka,
elx: elx,
eng: eng,
enm: enm,
epo: epo,
est: est,
ewe: ewe,
ewo: ewo,
fan: fan,
fao: fao,
fat: fat,
fij: fij,
fil: fil,
fin: fin,
fiu: fiu,
fon: fon,
fre: fre,
frm: frm,
fro: fro,
frr: frr,
frs: frs,
fry: fry,
ful: ful,
fur: fur,
gaa: gaa,
gay: gay,
gba: gba,
gem: gem,
geo: geo,
ger: ger,
gez: gez,
gil: gil,
gla: gla,
gle: gle,
glg: glg,
glv: glv,
gmh: gmh,
goh: goh,
gon: gon,
gor: gor,
got: got,
grb: grb,
grc: grc,
gre: gre,
grn: grn,
gsw: gsw,
guj: guj,
gwi: gwi,
hai: hai,
hat: hat,
hau: hau,
haw: haw,
heb: heb,
her: her,
hil: hil,
him: him,
hin: hin,
hit: hit,
hmn: hmn,
hmo: hmo,
hrv: hrv,
hsb: hsb,
hun: hun,
hup: hup,
iba: iba,
ibo: ibo,
ice: ice,
ido: ido,
iii: iii,
ijo: ijo,
iku: iku,
ile: ile,
ilo: ilo,
ina: ina,
inc: inc,
ind: ind,
ine: ine,
inh: inh,
ipk: ipk,
ira: ira,
iro: iro,
ita: ita,
jav: jav,
jbo: jbo,
jpn: jpn,
jpr: jpr,
jrb: jrb,
kaa: kaa,
kab: kab,
kac: kac,
kal: kal,
kam: kam,
kan: kan,
kar: kar,
kas: kas,
kau: kau,
kaw: kaw,
kaz: kaz,
kbd: kbd,
kha: kha,
khi: khi,
khm: khm,
kho: kho,
kik: kik,
kin: kin,
kir: kir,
kmb: kmb,
kok: kok,
kom: kom,
kon: kon,
kor: kor,
kos: kos,
kpe: kpe,
krc: krc,
krl: krl,
kro: kro,
kru: kru,
kua: kua,
kum: kum,
kur: kur,
kut: kut,
lad: lad,
lah: lah,
lam: lam,
lao: lao,
lat: lat,
lav: lav,
lez: lez,
lim: lim,
lin: lin,
lit: lit,
lol: lol,
loz: loz,
ltz: ltz,
lua: lua,
lub: lub,
lug: lug,
lui: lui,
lun: lun,
luo: luo,
lus: lus,
mac: mac,
mad: mad,
mag: mag,
mah: mah,
mai: mai,
mak: mak,
mal: mal,
man: man,
mao: mao,
map: map,
mar: mar,
mas: mas,
may: may,
mdf: mdf,
mdr: mdr,
men: men,
mga: mga,
mic: mic,
min: min,
mis: mis,
mkh: mkh,
mlg: mlg,
mlt: mlt,
mnc: mnc,
mni: mni,
mno: mno,
moh: moh,
mon: mon,
mos: mos,
mul: mul,
mun: mun,
mus: mus,
mwl: mwl,
mwr: mwr,
myn: myn,
myv: myv,
nah: nah,
nai: nai,
nap: nap,
nau: nau,
nav: nav,
nbl: nbl,
nde: nde,
ndo: ndo,
nds: nds,
nep: nep,
"new": "newari",
nia: nia,
nic: nic,
niu: niu,
nno: nno,
nob: nob,
nog: nog,
non: non,
nor: nor,
nqo: nqo,
nso: nso,
nub: nub,
nwc: nwc,
nya: nya,
nym: nym,
nyn: nyn,
nyo: nyo,
nzi: nzi,
oci: oci,
oji: oji,
ori: ori,
orm: orm,
osa: osa,
oss: oss,
ota: ota,
oto: oto,
paa: paa,
pag: pag,
pal: pal,
pam: pam,
pan: pan,
pap: pap,
pau: pau,
peo: peo,
per: per,
phi: phi,
phn: phn,
pli: pli,
pol: pol,
pon: pon,
por: por,
pra: pra,
pro: pro,
pus: pus,
que: que,
raj: raj,
rap: rap,
rar: rar,
roa: roa,
roh: roh,
rom: rom,
rum: rum,
run: run,
rup: rup,
rus: rus,
sad: sad,
sag: sag,
sah: sah,
sai: sai,
sal: sal,
sam: sam,
san: san,
sas: sas,
sat: sat,
scn: scn,
sco: sco,
sel: sel,
sem: sem,
sga: sga,
sgn: sgn,
shn: shn,
sid: sid,
sin: sin,
sio: sio,
sit: sit,
sla: sla,
slo: slo,
slv: slv,
sma: sma,
sme: sme,
smi: smi,
smj: smj,
smn: smn,
smo: smo,
sms: sms,
sna: sna,
snd: snd,
snk: snk,
sog: sog,
som: som,
son: son,
sot: sot,
spa: spa,
srd: srd,
srn: srn,
srp: srp,
srr: srr,
ssa: ssa,
ssw: ssw,
suk: suk,
sun: sun,
sus: sus,
sux: sux,
swa: swa,
swe: swe,
syc: syc,
syr: syr,
tah: tah,
tai: tai,
tam: tam,
tat: tat,
tel: tel,
tem: tem,
ter: ter,
tet: tet,
tgk: tgk,
tgl: tgl,
tha: tha,
tib: tib,
tig: tig,
tir: tir,
tiv: tiv,
tkl: tkl,
tlh: tlh,
tli: tli,
tmh: tmh,
tog: tog,
ton: ton,
tpi: tpi,
tsi: tsi,
tsn: tsn,
tso: tso,
tuk: tuk,
tum: tum,
tup: tup,
tur: tur,
tut: tut,
tvl: tvl,
twi: twi,
tyv: tyv,
udm: udm,
uga: uga,
uig: uig,
ukr: ukr,
umb: umb,
und: und,
urd: urd,
uzb: uzb,
vai: vai,
ven: ven,
vie: vie,
vol: vol,
vot: vot,
wak: wak,
wal: wal,
war: war,
was: was,
wel: wel,
wen: wen,
wln: wln,
wol: wol,
xal: xal,
xho: xho,
yao: yao,
yap: yap,
yid: yid,
yor: yor,
ypk: ypk,
zap: zap,
zbl: zbl,
zen: zen,
zha: zha,
znd: znd,
zul: zul,
zun: zun,
zxx: zxx,
zza: zza
};
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TranslateLanguageService {
constructor() {
this.translateService = inject(TranslateService);
// Available languages (import)
this._availableLanguages = { de: de$5, en: en$5, fr: fr$5, it: it$5 };
}
// List of preferred languages
static { this.PREFERRED_LANGUAGES = ['eng', 'fre', 'ger', 'ita']; }
/**
* @param langCode - ISO 639-2 (3 characters)
* @param language - ISO 639-1 (2 characters)
* @return human language - string
*/
translate(langCode, language) {
const lang = language || this.translateService.currentLang;
if (!(lang in this._availableLanguages)) {
return langCode;
}
if (!(langCode in this._availableLanguages[lang])) {
return langCode;
}
return this._availableLanguages[lang][langCode];
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLanguageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLanguageService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLanguageService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TranslateLanguagePipe {
constructor() {
this.translateLanguage = inject(TranslateLanguageService);
}
/**
* transform language code to human language
* @param langCode - ISO 639-2 (3 characters)
* @param language - ISO 639-1 (2 characters)
*/
transform(langCode, language) {
return this.translateLanguage.translate(langCode, language);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLanguagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: TranslateLanguagePipe, isStandalone: false, name: "translateLanguage" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLanguagePipe, decorators: [{
type: Pipe,
args: [{
name: 'translateLanguage',
standalone: false
}]
}] });
var Add$3 = "Hinzufügen";
var Apply$3 = "Anwenden";
var Back$3 = "Zurück";
var Cancel$3 = "Abbrechen";
var Clear$3 = "Leeren";
var Confirmation$3 = "Bestätigung";
var Confirmed$3 = "Bestätigt";
var Delete$3 = "Löschen";
var Edit$3 = "Bearbeiten";
var Forbidden$3 = "Verboten";
var Information$3 = "Information";
var Load$3 = "Laden";
var OK$3 = "OK";
var Quit$3 = "Verlassen";
var Save$3 = "Speichern";
var Show$3 = "Anzeigen";
var Stay$3 = "Bleiben";
var Template$3 = "Vorlage";
var Unauthorized$3 = "Unerlaubt";
var Use$3 = "Verwenden";
var de$3 = "Deutsch";
var en$4 = "Englisch";
var fr$4 = "Französisch";
var it$4 = "Italienisch";
var search$3 = "Suche";
var de$4 = {
Add: Add$3,
"Add field": "Feld hinzufügen",
"An error occurred": "Ein Fehler ist aufgetreten",
Apply: Apply$3,
Back: Back$3,
"Bad request": "Ungültige Anfrage",
Cancel: Cancel$3,
"Choose a name for your template": "Einen Namen für die Vorlage wählen",
Clear: Clear$3,
Confirmation: Confirmation$3,
Confirmed: Confirmed$3,
Delete: Delete$3,
"Do you really want to delete this record?": "Möchten Sie diesen Datensatz wirklich löschen?",
"Do you want to quit the page? The changes made so far will be lost.": "Möchten Sie die Seite verlassen? Die bisher vorgenommenen Änderungen gehen verloren.",
Edit: Edit$3,
"Edit mode": "Bearbeitungsmodus",
"Enter a query to get results.": "Geben Sie eine Abfrage ein, um Ergebnisse zu erhalten.",
"Error": "Fehler",
"Export as": "Exportieren als",
"Field(s) in error: ": "Fehlerhafte(s) Feld(er): ",
Forbidden: Forbidden$3,
"Generate a new password": "Ein neues Passwort generieren",
Information: Information$3,
"Jump to": "Gehe zu",
"Last month": "Letzten Monat",
"Last year": "Letztes Jahr",
Load: Load$3,
"Load from template": "Eine Vorlage laden",
"Load template…": "Vorlage laden…",
"Loading a template will reload the current form. All values previously encoded will be lost.": "Beim Laden einer Vorlage wird das aktuelle Formular neu geladen. Alle zuvor erfassten Werte gehen dabei verloren.",
"My private templates": "Meine privaten Vorlagen",
"No result": "Kein Ergebnis",
"Not found": "Nicht gefunden",
"Number of chars": "Anzahl der Zeichen",
"Number of words": "Anzahl der Wörter",
OK: OK$3,
"Public templates": "Öffentliche Vorlagen",
Quit: Quit$3,
"Quit the page": "Seite verlassen",
"Record created.": "Ressource erstellt.",
"Record deleted.": "Datensatz gelöscht.",
"Record updated.": "Daten aktualisiert.",
"Result list is empty.": "Die Ergebnisliste ist leer.",
Save: Save$3,
"Save as template": "Als Vorlage speichern",
"Search in": "Suchen in",
"Select a template": "Eine Vorlage wählen",
"Select…": "Wählen…",
"Server error.": "Serverfehler.",
Show: Show$3,
"Show or hide password": "Passwort anzeigen oder verbergen",
"Sort by": "Sortieren nach",
Stay: Stay$3,
Template: Template$3,
"Template loaded": "Vorlage geladen",
"The form contains errors.": "Das Formular enthält Fehler.",
"The password has been copied to the clipboard.": "Das Kennwort wurde in die Zwischenablage kopiert.",
"There are no records in this section": "In diesem Abschnitt gibt es keine Ressourcen",
"This field is required": "Dieses Feld ist erforderlich",
"This month": "Diesen Monat",
"This year": "Dieses Jahr",
"Too many items. Should be less than {{max}}.": "Zu viele Artikel. Sollte weniger als {{max}} sein.",
Unauthorized: Unauthorized$3,
Use: Use$3,
"Warning!": "Achtung!",
"You cannot access this page.": "Sie können nicht auf diese Seite zugreifen.",
"You cannot read this record": "Sie dürfen diesen Datensatz nicht lesen",
"You cannot update this record": "Sie dürfen diesen Datensatz nicht aktualisieren",
"between {{min}} and {{max}}": "zwischen {{min}} und {{max}}",
de: de$3,
en: en$4,
fr: fr$4,
it: it$4,
"less…": "Weniger…",
"maximum {{max}}": "maximal {{max}}",
"minimum {{min}}": "mindestens {{min}}",
"more…": "Mehr…",
search: search$3,
"should NOT be longer than {{maxLength}} characters": "darf nicht länger als {{maxLength}} Zeichen sein",
"should NOT be shorter than {{minLength}} characters": "darf nicht kürzer als {{minLength}} Zeichen sein",
"should NOT have duplicate items": "sollte NICHT doppelt vorhanden sein",
"should NOT have fewer than {{minItems}} items": "muss mindestens {{minItems}} Elemente haben",
"should NOT have more than {{maxItems}} items": "darf höchstens {{maxItems}} Elemente haben",
"should be < {{step}}": "muss kleiner als {{step}} sein",
"should be <= {{max}}": "muss grösser oder gleich {{max}} sein",
"should be > {{step}}": "muss grösser als {{step}} sein",
"should be >= {{min}}": "muss kleiner oder gleich {{min}} sein",
"should be equal to constant \"{{const}}\"": "muss gleich der Konstante \"{{const}}\" sein",
"should be multiple of ${{step}}": "muss ein Vielfaches von ${{step}} sein",
"should be null": "sollte null sein",
"should have {{counter}} {{keys}}": "muss {{counter}} {{keys}} haben",
"strictly {{counter}}": "streng {{counter}}",
"the value is already taken": "der Wert ist bereits vergeben",
"{{ total }} result": "{{ total }} Ergebnis",
"{{ total }} results": "{{ total }} Ergebnisse"
};
var Add$2 = "Add";
var Apply$2 = "Apply";
var Back$2 = "Back";
var Cancel$2 = "Cancel";
var Clear$2 = "Clear";
var Confirmation$2 = "Confirmation";
var Confirmed$2 = "Confirmed";
var Delete$2 = "Delete";
var Edit$2 = "Edit";
var Forbidden$2 = "Forbidden";
var Information$2 = "Information";
var Load$2 = "Load";
var OK$2 = "OK";
var Quit$2 = "Quit";
var Save$2 = "Save";
var Show$2 = "Show";
var Stay$2 = "Stay";
var Template$2 = "Template";
var Unauthorized$2 = "Unauthorized";
var Use$2 = "Use";
var de$2 = "German";
var en$2 = "English";
var fr$3 = "French";
var it$3 = "Italian";
var search$2 = "search";
var en$3 = {
Add: Add$2,
"Add field": "Add field",
"An error occurred": "An error occurred",
Apply: Apply$2,
Back: Back$2,
"Bad request": "Bad request",
Cancel: Cancel$2,
"Choose a name for your template": "Choose a name for your template",
Clear: Clear$2,
Confirmation: Confirmation$2,
Confirmed: Confirmed$2,
Delete: Delete$2,
"Do you really want to delete this record?": "Do you really want to delete this record?",
"Do you want to quit the page? The changes made so far will be lost.": "Do you want to quit the page? The changes made so far will be lost.",
Edit: Edit$2,
"Edit mode": "Edit mode",
"Enter a query to get results.": "Enter a query to get results.",
"Error": "Error",
"Export as": "Export as",
"Field(s) in error: ": "Field(s) in error: ",
Forbidden: Forbidden$2,
"Generate a new password": "Generate a new password",
Information: Information$2,
"Jump to": "Jump to",
"Last month": "Last month",
"Last year": "Last year",
Load: Load$2,
"Load from template": "Load from template",
"Load template…": "Load template…",
"Loading a template will reload the current form. All values previously encoded will be lost.": "Loading a template will reload the current form. All values previously encoded will be lost.",
"My private templates": "My private templates",
"No result": "No result",
"Not found": "Not found",
"Number of chars": "Number of chars",
"Number of words": "Number of words",
OK: OK$2,
"Public templates": "Public templates",
Quit: Quit$2,
"Quit the page": "Quit the page",
"Record created.": "Record created.",
"Record deleted.": "Record deleted.",
"Record updated.": "Record updated.",
"Result list is empty.": "Result list is empty.",
Save: Save$2,
"Save as template": "Save as template",
"Search in": "Search in",
"Select a template": "Select a template",
"Select…": "Select…",
"Server error.": "Server error.",
Show: Show$2,
"Show or hide password": "Show or hide password",
"Sort by": "Sort by",
Stay: Stay$2,
Template: Template$2,
"Template loaded": "Template loaded",
"The form contains errors.": "The form contains errors.",
"The password has been copied to the clipboard.": "The password has been copied to the clipboard.",
"There are no records in this section": "There are no records in this section",
"This field is required": "This field is required",
"This month": "This month",
"This year": "This year",
"Too many items. Should be less than {{max}}.": "Too many items. Should be less than {{max}}.",
Unauthorized: Unauthorized$2,
Use: Use$2,
"Warning!": "Warning!",
"You cannot access this page.": "You cannot access this page.",
"You cannot read this record": "You cannot read this record",
"You cannot update this record": "You cannot update this record",
"between {{min}} and {{max}}": "between {{min}} and {{max}}",
de: de$2,
en: en$2,
fr: fr$3,
it: it$3,
"less…": "less…",
"maximum {{max}}": "maximum {{max}}",
"minimum {{min}}": "minimum {{min}}",
"more…": "more…",
search: search$2,
"should NOT be longer than {{maxLength}} characters": "should NOT be longer than {{maxLength}} characters",
"should NOT be shorter than {{minLength}} characters": "should NOT be shorter than {{minLength}} characters",
"should NOT have duplicate items": "should NOT have duplicate items",
"should NOT have fewer than {{minItems}} items": "should NOT have fewer than {{minItems}} items",
"should NOT have more than {{maxItems}} items": "should NOT have more than {{maxItems}} items",
"should be < {{step}}": "should be < {{step}}",
"should be <= {{max}}": "should be <= {{max}}",
"should be > {{step}}": "should be > {{step}}",
"should be >= {{min}}": "should be >= {{min}}",
"should be equal to constant \"{{const}}\"": "should be equal to constant \"{{const}}\"",
"should be multiple of ${{step}}": "should be multiple of ${{step}}",
"should be null": "should be null",
"should have {{counter}} {{keys}}": "should have {{counter}} {{keys}}",
"strictly {{counter}}": "strictly {{counter}}",
"the value is already taken": "the value is already taken",
"{{ total }} result": "{{ total }} result",
"{{ total }} results": "{{ total }} results"
};
var Add$1 = "Ajouter";
var Apply$1 = "Appliquer";
var Back$1 = "Retour";
var Cancel$1 = "Annuler";
var Clear$1 = "Effacer";
var Confirmation$1 = "Confirmation";
var Confirmed$1 = "Confirmé";
var Delete$1 = "Supprimer";
var Edit$1 = "Modifier";
var Forbidden$1 = "Interdit";
var Information$1 = "Information";
var Load$1 = "Charger";
var OK$1 = "OK";
var Quit$1 = "Quitter";
var Save$1 = "Enregistrer";
var Show$1 = "Afficher";
var Stay$1 = "Rester";
var Template$1 = "Modèle";
var Unauthorized$1 = "Non autorisé";
var Use$1 = "Utiliser";
var de$1 = "Allemand";
var en$1 = "Anglais";
var fr$1 = "Français";
var it$2 = "Italien";
var search$1 = "recherche";
var fr$2 = {
Add: Add$1,
"Add field": "Ajouter un champ",
"An error occurred": "Une erreur est survenue",
Apply: Apply$1,
Back: Back$1,
"Bad request": "Requête invalide",
Cancel: Cancel$1,
"Choose a name for your template": "Choisir un nom pour votre modèle",
Clear: Clear$1,
Confirmation: Confirmation$1,
Confirmed: Confirmed$1,
Delete: Delete$1,
"Do you really want to delete this record?": "Voulez-vous vraiment supprimer cet enregistrement ?",
"Do you want to quit the page? The changes made so far will be lost.": "Voulez-vous quitter la page ? Les modifications seront perdues.",
Edit: Edit$1,
"Edit mode": "Mode édition",
"Enter a query to get results.": "Entrer une requête pour obtenir des résultats.",
"Error": "Erreur",
"Export as": "Exporter en",
"Field(s) in error: ": "Champ(s) en erreur : ",
Forbidden: Forbidden$1,
"Generate a new password": "Générer un nouveau mot de passe",
Information: Information$1,
"Jump to": "Aller à",
"Last month": "Le mois dernier",
"Last year": "L'année dernière",
Load: Load$1,
"Load from template": "Charger un modèle",
"Load template…": "Charger le modèle…",
"Loading a template will reload the current form. All values previously encoded will be lost.": "Le chargement d'un modèle va recharger le formulaire actuel. Toutes les valeurs saisies jusqu'à maintenant seront perdues.",
"My private templates": "Mes modèles privés",
"No result": "Aucun résultat",
"Not found": "Non trouvé",
"Number of chars": "Nombre de caractères",
"Number of words": "Nombre de mots",
OK: OK$1,
"Public templates": "Modèles publics",
Quit: Quit$1,
"Quit the page": "Quitter la page",
"Record created.": "Ressource créée.",
"Record deleted.": "Ressource effacée.",
"Record updated.": "Ressource mise à jour.",
"Result list is empty.": "La liste de résultat est vide.",
Save: Save$1,
"Save as template": "Enregistrer en tant que modèle",
"Search in": "Chercher dans",
"Select a template": "Sélectionner un modèle",
"Select…": "Sélectionner…",
"Server error.": "Erreur serveur.",
Show: Show$1,
"Show or hide password": "Afficher/masquer le mot de passe",
"Sort by": "Trier par",
Stay: Stay$1,
Template: Template$1,
"Template loaded": "Modèle chargé",
"The form contains errors.": "Le formulaire contient des erreurs.",
"The password has been copied to the clipboard.": "Le mot de passe a été copié dans le presse-papiers.",
"There are no records in this section": "Il n'y a pas de ressources dans cette section",
"This field is required": "Ce champ est obligatoire",
"This month": "Ce mois-ci",
"This year": "Cette année",
"Too many items. Should be less than {{max}}.": "Trop d'éléments. Le total doit être inférieur à {{max}}.",
Unauthorized: Unauthorized$1,
Use: Use$1,
"Warning!": "Attention !",
"You cannot access this page.": "Vous ne pouvez pas accéder à cette page.",
"You cannot read this record": "Vous n'avez pas les permissions nécessaires pour afficher cette ressource",
"You cannot update this record": "Vous n'avez pas les permissions nécessaires pour modifier cette ressource",
"between {{min}} and {{max}}": "entre {{min}} et {{max}}",
de: de$1,
en: en$1,
fr: fr$1,
it: it$2,
"less…": "moins…",
"maximum {{max}}": "maximum {{max}}",
"minimum {{min}}": "minimum {{min}}",
"more…": "plus…",
search: search$1,
"should NOT be longer than {{maxLength}} characters": "Ne doit pas être plus long que {{maxLength}} caractères",
"should NOT be shorter than {{minLength}} characters": "Ne doit pas être plus court que {{minLength}} caractères",
"should NOT have duplicate items": "Deux éléments identiques ne sont pas autorisés",
"should NOT have fewer than {{minItems}} items": "Doit contenir au moins {{minItems}} éléments",
"should NOT have more than {{maxItems}} items": "Doit contenir au maximum {{maxItems}} éléments",
"should be < {{step}}": "Doit être plus petit que {{step}}",
"should be <= {{max}}": "Doit être plus petit ou égal à {{max}}",
"should be > {{step}}": "Doit être plus grand que {{step}}",
"should be >= {{min}}": "Doit être plus grand ou égal à {{min}}",
"should be equal to constant \"{{const}}\"": "Doit être égal à la constante \"{{const}}\"",
"should be multiple of ${{step}}": "Doit être un multiple de ${{step}}",
"should be null": "doit être vide",
"should have {{counter}} {{keys}}": "doit contenir {{counter}} {{keys}}",
"strictly {{counter}}": "strictement {{counter}}",
"the value is already taken": "cette valeur est déjà utilisée",
"{{ total }} result": "{{ total }} résultat",
"{{ total }} results": "{{ total }} résultats"
};
var Add = "Aggiungere";
var Apply = "Applicare";
var Back = "Indietro";
var Cancel = "Annullare";
var Clear = "Cancellare";
var Confirmation = "Conferma";
var Confirmed = "Confermato";
var Delete = "Eliminare";
var Edit = "Modificare";
var Forbidden = "Vietato";
var Information = "Informazione";
var Load = "Caricare";
var OK = "OK";
var Quit = "Uscire";
var Save = "Salvare";
var Show = "Mostrare";
var Stay = "Rimanere";
var Template = "Modello";
var Unauthorized = "Non autorizzato";
var Use = "Utilizzare";
var de = "Tedesco";
var en = "Inglese";
var fr = "Francese";
var it = "Italiano";
var search = "ricerca";
var it$1 = {
Add: Add,
"Add field": "Aggiungere un campo",
"An error occurred": "Si è verificato un errore",
Apply: Apply,
Back: Back,
"Bad request": "Richiesta non valida",
Cancel: Cancel,
"Choose a name for your template": "Scegliere un nome per il modello",
Clear: Clear,
Confirmation: Confirmation,
Confirmed: Confirmed,
Delete: Delete,
"Do you really want to delete this record?": "Si desidera veramente eliminare questa risorsa?",
"Do you want to quit the page? The changes made so far will be lost.": "Vuoi uscire dalla pagina? Le modifiche fatte finora andranno perse.",
Edit: Edit,
"Edit mode": "Modalità di modifica",
"Enter a query to get results.": "Inserisci una richiesta per ottenere dei risultati.",
"Error": "Errore",
"Export as": "Esportare in",
"Field(s) in error: ": "Campo/i in errore: ",
Forbidden: Forbidden,
"Generate a new password": "Generare una nuova password",
Information: Information,
"Jump to": "Andare a",
"Last month": "Ultimo mese",
"Last year": "Ultimo anno",
Load: Load,
"Load from template": "Caricare un modello",
"Load template…": "Caricare modello…",
"Loading a template will reload the current form. All values previously encoded will be lost.": "Il caricamento di un modello ricaricherà il modulo corrente. Tutti i valori precedentemente inseriti andranno persi.",
"My private templates": "I miei modelli privati",
"No result": "Nessun risultato",
"Not found": "Non trovato",
"Number of chars": "Numero di caratteri",
"Number of words": "Numero di parole",
OK: OK,
"Public templates": "Modelli pubblici",
Quit: Quit,
"Quit the page": "Uscire dalla pagina",
"Record created.": "Record creato.",
"Record deleted.": "Record eliminato.",
"Record updated.": "Record aggiornato.",
"Result list is empty.": "L'elenco dei risultati è vuoto.",
Save: Save,
"Save as template": "Salvare come modello",
"Search in": "Cercare in",
"Select a template": "Selezionare un modello",
"Select…": "Selezionare…",
"Server error.": "Errore del server.",
Show: Show,
"Show or hide password": "Mostra o nascondi la password",
"Sort by": "Ordina per",
Stay: Stay,
Template: Template,
"Template loaded": "Modello caricato",
"The form contains errors.": "Il modulo contiene errori.",
"The password has been copied to the clipboard.": "La password è stata copiata negli appunti.",
"There are no records in this section": "Non ci sono risorse in questa sezione",
"This field is required": "Questo campo è obbligatorio",
"This month": "Questo mese",
"This year": "Quest'anno",
"Too many items. Should be less than {{max}}.": "Troppi elementi. Dovrebbe essere meno di {{max}}.",
Unauthorized: Unauthorized,
Use: Use,
"Warning!": "Attenzione!",
"You cannot access this page.": "Non è possibile accedere a questa pagina.",
"You cannot read this record": "Lei non ha autorizzazioni sufficienti per vedere questa risorsa",
"You cannot update this record": "Non è abilitato a modificare questo record",
"between {{min}} and {{max}}": "tra {{min}} e {{max}}",
de: de,
en: en,
fr: fr,
it: it,
"less…": "Meno…",
"maximum {{max}}": "massimo {{max}}",
"minimum {{min}}": "minimo {{min}}",
"more…": "Più…",
search: search,
"should NOT be longer than {{maxLength}} characters": "Non deve essere più lungo di {{maxLength}} caratteri",
"should NOT be shorter than {{minLength}} characters": "Non deve avere meno di {{minLength}} caratteri",
"should NOT have duplicate items": "NON deve presentare due elementi identici",
"should NOT have fewer than {{minItems}} items": "NON deve avere meno di {{minItems}} esemplari",
"should NOT have more than {{maxItems}} items": "NON deve avere più di {{maxItems}} esemplari",
"should be < {{step}}": "Deve essere inferiore a {{step}}",
"should be <= {{max}}": "Deve essere inferiore o uguale a {{max}}",
"should be > {{step}}": "Deve essere maggiore a > {{step}}",
"should be >= {{min}}": "Deve essere maggiore o uguale a {{min}}",
"should be equal to constant \"{{const}}\"": "Deve essere uguale alla costante \"{{const}}\"",
"should be multiple of ${{step}}": "Deve essere un multiplo di ${{step}}",
"should be null": "Deve essere vuoto",
"should have {{counter}} {{keys}}": "deve avere {{counter}} {{keys}}",
"strictly {{counter}}": "rigorosamente {{counter}}",
"the value is already taken": "Il valore è già utilizzato",
"{{ total }} result": "{{ total }} risultato",
"{{ total }} results": "{{ total }} risultati"
};
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Loader for translations used in ngx-translate library.
*/
class TranslateLoader {
constructor() {
this.coreConfigService = inject(CoreConfigService);
this.http = inject(HttpClient);
// translations
this.translations = {};
// locale translations
// with angular<9 assets are not available for libraries
// See: https://angular.io/guide/creating-libraries#managing-assets-in-a-library
this.coreTranslations = { de: de$4, en: en$3, fr: fr$2, it: it$1 };
}
/**
* Return observable used by ngx-translate to get translations.
* @param lang - string, language to retrieve translations from.
*/
getTranslation(lang) {
// Already in cache
if (this.translations[lang] != null) {
return of(this.translations[lang]);
}
const urls = this.coreConfigService.translationsURLs;
// create the list of http requests
const observers = urls.map(url => {
const langURL = url.replace('${lang}', lang);
return this.http.get(langURL).pipe(catchError(() => {
console.log(`ERROR: Cannot load translation: ${langURL}`);
return of([]);
}));
});
// Get local and backend translations
return forkJoin(observers).pipe(map$4((translations) => {
// copy local translations
if (this.coreTranslations[lang] != null) {
this.translations[lang] = { ...this.coreTranslations[lang] };
}
// get remote translations
translations.map(trans => this.translations[lang] = {
...this.translations[lang],
...trans
});
return this.translations[lang];
}));
}
}
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class NgCoreTranslateService extends TranslateService {
constructor() {
super(...arguments);
this.primeNG = inject(PrimeNG);
this.coreConfigService = inject(CoreConfigService);
this.locales = {
de: { angular: localeDe, primeng: de$6 },
en: { angular: localeEn, primeng: en$6 },
fr: { angular: localeFr, primeng: fr$6 },
it: { angular: localeIt, primeng: it$6 }
};
}
initialize() {
for (const [key, value] of Object.entries(this.locales)) {
registerLocaleData(value.angular, key);
}
const languages = this.coreConfigService.languages;
super.addLangs(languages);
super.onLangChange.subscribe(translation => {
this.use(translation.lang);
});
super.setDefaultLang(this.coreConfigService.defaultLanguage);
if (!super.currentLang) {
super.use(this.coreConfigService.defaultLanguage);
}
}
use(lang) {
DateTime.locale = lang;
this.primeNG.setTranslation(this.locales[lang].primeng[lang]);
return super.use(lang);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreTranslateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreTranslateService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreTranslateService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class CoreModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CoreModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: CoreModule, declarations: [Nl2brPipe,
DefaultPipe,
TruncateTextPipe,
UpperCaseFirstPipe,
CallbackArrayFilterPipe,
DateTranslatePipe,
DialogComponent,
SearchInputComponent,
TranslateLanguagePipe,
TextReadMoreComponent,
SortByKeysPipe,
ErrorComponent,
FilesizePipe,
MarkdownPipe,
SanitizePipe], imports: [CommonModule,
RouterModule, i5.TranslateModule, NgxSpinnerModule,
PrimeNgCoreModule], exports: [PrimeNgCoreModule,
CommonModule,
TranslateModule,
Nl2brPipe,
DefaultPipe,
TruncateTextPipe,
TranslateLanguagePipe,
UpperCaseFirstPipe,
CallbackArrayFilterPipe,
DateTranslatePipe,
FilesizePipe,
DialogComponent,
SearchInputComponent,
TextReadMoreComponent,
ErrorComponent,
SortByKeysPipe,
NgxSpinnerModule,
MarkdownPipe,
SanitizePipe] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CoreModule, providers: [
provideAppInitializer(() => {
const translateService = inject(NgCoreTranslateService);
translateService.initialize();
return of(true);
}),
{ provide: TranslateService, useClass: NgCoreTranslateService },
{ provide: TitleStrategy, useClass: PageTitleStrategy },
ComponentCanDeactivateGuard,
ConfirmationService,
MessageService,
SanitizePipe
], imports: [CommonModule,
RouterModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader$1,
useClass: TranslateLoader,
deps: [CoreConfigService, HttpClient]
}
}),
NgxSpinnerModule,
PrimeNgCoreModule, PrimeNgCoreModule,
CommonModule,
TranslateModule,
NgxSpinnerModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CoreModule, decorators: [{
type: NgModule,
args: [{
declarations: [
Nl2brPipe,
DefaultPipe,
TruncateTextPipe,
UpperCaseFirstPipe,
CallbackArrayFilterPipe,
DateTranslatePipe,
DialogComponent,
SearchInputComponent,
TranslateLanguagePipe,
TextReadMoreComponent,
SortByKeysPipe,
ErrorComponent,
FilesizePipe,
MarkdownPipe,
SanitizePipe
],
imports: [
CommonModule,
RouterModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader$1,
useClass: TranslateLoader,
deps: [CoreConfigService, HttpClient]
}
}),
NgxSpinnerModule,
PrimeNgCoreModule
],
exports: [
PrimeNgCoreModule,
CommonModule,
TranslateModule,
Nl2brPipe,
DefaultPipe,
TruncateTextPipe,
TranslateLanguagePipe,
UpperCaseFirstPipe,
CallbackArrayFilterPipe,
DateTranslatePipe,
FilesizePipe,
DialogComponent,
SearchInputComponent,
TextReadMoreComponent,
ErrorComponent,
SortByKeysPipe,
NgxSpinnerModule,
MarkdownPipe,
SanitizePipe
],
providers: [
provideAppInitializer(() => {
const translateService = inject(NgCoreTranslateService);
translateService.initialize();
return of(true);
}),
{ provide: TranslateService, useClass: NgCoreTranslateService },
{ provide: TitleStrategy, useClass: PageTitleStrategy },
ComponentCanDeactivateGuard,
ConfirmationService,
MessageService,
SanitizePipe
]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Initialize the Formly widget on JSONSchema
* @param schema - object, the JSONSchema
*/
function createWidgetFormlyConfigProps(schema) {
if (!schema.widget) {
schema.widget = {};
}
if (!schema.widget.formlyConfig) {
schema.widget.formlyConfig = {};
}
if (!schema.widget.formlyConfig.props) {
schema.widget.formlyConfig.props = {};
}
}
/**
* Resolve $ref definition and set properties on field
* @param schema - json schema all properties
* @param schemaProperties - json schema level
* @returns the updated schema
*/
function resolve$ref(schema, schemaProperties) {
if (schemaProperties) {
Object.keys(schemaProperties).forEach((property) => {
let field = schemaProperties[property];
if (field.properties) {
resolve$ref(schema, field.properties);
}
// The field contains a $ref definition
if (field.$ref) {
const paths = field.$ref.replace('#/', '').split('/');
let def = schema;
paths.forEach((path) => {
def = def[path];
});
// Populate field with new definition
Object.keys(def).forEach((defKey) => {
if (defKey === 'widget' && def[defKey].formlyConfig?.templateOptions) {
def[defKey].formlyConfig.props = def[defKey].formlyConfig.templateOptions;
delete def[defKey].formlyConfig.templateOptions;
}
field[defKey] = def[defKey];
});
// Delete reference
delete field.$ref;
}
});
}
return schema;
}
/**
* Process required properties on the JSONSchema.
* @param schema - object, the JSONSchema
* @returns object, a processed required properties JSONSchema
*/
function processRequiredJsonSchema(schema) {
schema.required.map((key) => {
const childSchema = schema.properties[key];
if (childSchema) {
createWidgetFormlyConfigProps(childSchema);
childSchema.widget.formlyConfig.props.initialRequired = true;
}
});
return schema;
}
/**
* Process order properties on the JSONSchema
* @param schema - object, the JSONSchema
* @returns object, a processed order properties JSONSchema
*/
function processPropertiesOder(schema) {
// copy the data
schema._properties = { ...schema.properties };
// new ordered properties
schema.properties = {};
// copy in the right order
for (const property of schema.propertiesOrder) {
schema.properties[property] = schema._properties[property];
}
return schema;
}
/**
* Process JSONSchema
* This re-order the object properties given a local defined
* `propertiesOrder` property.
* The process the required properties given a local defined
* `required` property.
* @param schema - object, the JSONSchema
* @returns object, a converted JSONSchema
*/
function processJsonSchema(schema) {
if (schema.properties) {
if (schema.propertiesOrder) {
schema = processPropertiesOder(schema);
}
if (schema.required) {
schema = processRequiredJsonSchema(schema);
}
// recursion for objects
for (const property of Object.keys(schema.properties)) {
processJsonSchema(schema.properties[property]);
}
}
// recursion for array
if (schema.items) {
processJsonSchema(schema.items);
}
// recursion for oneOf
if (schema.oneOf) {
for (const item of schema.oneOf) {
processJsonSchema(item);
}
}
// recursion for anyOf
if (schema.anyOf) {
for (const item of schema.anyOf) {
processJsonSchema(item);
}
}
// recursion for allOf
if (schema.allOf) {
for (const item of schema.allOf) {
processJsonSchema(item);
}
}
// recursion for definitions
if (schema.definitions) {
for (const property of Object.keys(schema.definitions)) {
processJsonSchema(schema.definitions[property]);
}
}
return schema;
}
/**
* Replace $ref by pid
*
* @param schema - object, the JSONSchema
* @param data - object, the data to be cleaned
* @returns object, a fresh copy of the data with replacements
*/
function resolveRefs(data) {
// array?
if (data instanceof Array) {
for (const d of data) {
// recursion
const value = resolveRefs(d);
}
}
// object
if (data instanceof Object) {
// new object with resolved refs
const newObject = {};
for (const key of Object.keys(data)) {
const value = resolveRefs(data[key]);
if (key === '$ref') {
newObject.pid = extractIdOnRef(value);
}
else {
newObject[key] = value;
}
}
return newObject;
}
return data;
}
/**
* Tell if a value can be considered as empty
* @param value - any, the value to check
* @returns boolean, true if the value is empty
*/
function isEmpty(value) {
return (
// null or undefined
value == null ||
// has length and it's zero (array, string)
(value.hasOwnProperty('length') && value.length === 0) ||
// is an Object and has no keys
(value instanceof Object && Object.keys(value).length === 0));
}
/**
* Recursively remove the empty values
* @param data - object, the data to be cleaned
* @returns object, a fresh copy of the clean data
*/
function removeEmptyValues(data) {
// array?
if (data instanceof Array) {
// new array with non empty values
const newArray = [];
for (const d of data) {
// recursion
const value = removeEmptyValues(d);
if (!isEmpty(value)) {
newArray.push(value);
}
}
return newArray;
}
// object?
if (data instanceof Object) {
// new object with non empty values
const newObject = {};
for (const key of Object.keys(data)) {
const value = removeEmptyValues(data[key]);
if (!isEmpty(value)) {
newObject[key] = value;
}
}
return newObject;
}
return data;
}
class RecordHandleErrorService {
constructor() {
this.translateService = inject(TranslateService);
}
/**
* handle http error
* @param error - HttpErrorResponse
* @returns Observable
*/
handleError(error, resourceName) {
return this.standardHandleError(error);
}
/**
* Standard handle error (base error)
* @param error - HttpErrorResponse
* @returns Observable
*/
standardHandleError(error) {
// handle Marshmallow errors
// Python marshmallow library can raise ValidationError. In this case, the message is simply 'Validation error'
// but list of errors are passed into the `errors` fields.
if (error.error?.errors) {
const message = typeof (error.error.errors) === 'object'
? error.error.errors.message
: error.error.errors
.map(err => this.translateService.instant(err.message))
.join('; ');
return throwError(() => ({ status: error.status, title: message }));
}
// check if we have possible custom error message to display
if (error.status === 400 && error.error.hasOwnProperty('message')) {
let { message } = error.error;
message = message.replace(/^Validation error: /, '').trim(); // Remove Invenio `ValidationError` header
return throwError(() => ({ status: error.status, title: this.translateService.instant(message) }));
}
// If not, then return default message
switch (error.status) {
case 400:
return throwError(() => ({ status: error.status, title: this.translateService.instant('Bad request') }));
case 401:
return throwError(() => ({ status: error.status, title: this.translateService.instant('Unauthorized') }));
case 403:
return throwError(() => ({ status: error.status, title: this.translateService.instant('Forbidden') }));
case 404:
return throwError(() => ({ status: error.status, title: this.translateService.instant('Not found') }));
default:
return throwError(() => ({ status: 500, title: this.translateService.instant('An error occurred') }));
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordHandleErrorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordHandleErrorService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordHandleErrorService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordService {
constructor() {
this.http = inject(HttpClient);
this.apiService = inject(ApiService);
this.translateService = inject(TranslateService);
this.recordHandleErrorService = inject(RecordHandleErrorService);
/**
* Event for record created
*/
this.onCreate = new Subject();
/**
* Event for record updated
*/
this.onUpdate = new Subject();
/**
* Event for record deleted
*/
this.onDelete = new Subject();
}
static { this.DEFAULT_REST_RESULTS_SIZE = 10; }
static { this.MAX_REST_RESULTS_SIZE = 9999; }
/**
* On create observable
* @return onCreate Subject
*/
get onCreate$() {
return this.onCreate.asObservable();
}
/**
* On update observable
* @return onUpdate Subject
*/
get onUpdate$() {
return this.onUpdate.asObservable();
}
/**
* On delete observable
* @return onDelete Subject
*/
get onDelete$() {
return this.onDelete.asObservable();
}
/**
* Get records filtered by given parameters.
*
* @param type - string, type of resource
* @param query - string, keyword to search for
* @param page - number, return records corresponding to this page
* @param itemsPerPage - number, number of records to return
* @param aggregationsFilters - list, option list of filters; usually used by
* the aggregation filters.
* @param preFilters - object, option list of additional filters. The value can
* a string or a list of string to filter with multiple values.
* @param headers - HttpHeaders optional http header for the backend call.
* @param sort - parameter for sorting records (eg. 'mostrecent' or '-mostrecent')
* @param facets - list of strings, define which aggregations/facets should be included into the response.
*/
getRecords(type, query = '', page = 1, itemsPerPage = RecordService.DEFAULT_REST_RESULTS_SIZE, aggregationsFilters = [], preFilters = {}, headers = null, sort = null, facets = []) {
// Build query string
let httpParams = new HttpParams().set('q', query);
httpParams = httpParams.append('page', '' + page);
httpParams = httpParams.append('size', '' + itemsPerPage);
if (sort) {
httpParams = httpParams.append('sort', sort);
}
// aggregationsFilters
aggregationsFilters.map((filter) => {
filter.values.map((value) => {
httpParams = httpParams.append(filter.key, value);
});
});
// preFilters
for (const key of Object.keys(preFilters)) {
const value = preFilters[key];
if (Array.isArray(value)) {
value.map((val) => {
httpParams = httpParams.append(key, val);
});
}
else {
httpParams = httpParams.append(key, value);
}
}
// facets management
// if array `facets` is an empty array, no aggregations data will be included into response.s
httpParams = httpParams.append('facets', facets.join(','));
// http request with headers
return this.http
.get(this.apiService.getEndpointByType(type, true) + '/', {
params: httpParams,
headers: this._createRequestHeaders(headers),
})
.pipe(catchError((error) => this._handleError(error, type)));
}
/**
* Delete a record.
* @param type - string, type of record
* @param pid - string, PID to remove
*/
delete(type, pid) {
return this.http
.delete(this.apiService.getEndpointByType(type, true) + '/' + pid)
.pipe(catchError((error) => this._handleError(error, type)), tap(() => this.onDelete.next(this._createEvent(type, { pid }))));
}
/**
* Get record data
* @param type - string, type of resource
* @param pid - string, record PID
*/
getRecord(type, pid, resolve = 0, headers = {}) {
return this.http
.get(`${this.apiService.getEndpointByType(type, true)}/${pid}?resolve=${resolve}`, {
headers: this._createRequestHeaders(headers),
})
.pipe(catchError((error) => this._handleError(error, type)));
}
/**
* Return the schema form to generate the form based on the resource given.
* @param recordType - string, type of the resource
*/
getSchemaForm(recordType) {
let recType = recordType.replace(/ies$/, 'y');
recType = recType.replace(/s$/, '');
const url = this.apiService.getSchemaFormEndpoint(recordType, true);
return this.http.get(url).pipe(catchError((e) => {
if (e.status === 404) {
return of(null);
}
}), map$4((data) => {
return data;
}));
}
/**
* Create a new record
* @param recordType - string, type of resource
* @param record - object, record to create
*/
create(recordType, record) {
return this.http
.post(this.apiService.getEndpointByType(recordType, true) + '/', record)
.pipe(catchError((error) => this._handleError(error, recordType)), tap(() => this.onCreate.next(this._createEvent(recordType, { record }))));
}
/**
* Update a record
* @param recordType - string, type of resource
* @param pid - string, record PID
* @param record - object, record to update
*/
update(recordType, pid, record) {
const url = `${this.apiService.getEndpointByType(recordType, true)}/${pid}`;
return this.http
.put(url, record)
.pipe(catchError((error) => this._handleError(error, recordType)), tap(() => this.onUpdate.next(this._createEvent(recordType, { record }))));
}
/**
* Check if a record is already registered with the same value
* @param recordType - string, type of record
* @param field - string, field to check
* @param value - string, value to check
* @param excludePid - string, PID to ignore (normally the current record we are checking)
*/
valueAlreadyExists(recordType, field, value, excludePid) {
value = value.replace(/\"/g, '\\"');
let query = `${field}:"${value}"`;
if (excludePid) {
query += ` NOT pid:${excludePid}`;
}
return this.getRecords(recordType, query, 1, 1).pipe(map$4((res) => this.totalHits(res.hits.total)), map$4((total) => (total ? { alreadyTaken: value } : null)), debounceTime(1000));
}
/**
* Check if a record is already registered with the same value
* @param field - FormlyFieldConfig, field to check
* @param recordType - string, type of record
* @param excludePid - string, PID to ignore (normally the current record we are checking)
* @param term - string, the elasticsearch term to check the uniqueness, use field.key if not given
* @param limitToValues - string[], limit the test to a given list of values
* @param filter - string, additional es query filters
*/
uniqueValue(field, recordType, excludePid = null, term = null, limitToValues = [], filter = null) {
let key = field.key;
if (term != null) {
key = term;
}
let value = field.formControl.value;
const model = resolveRefs(field.model);
if (value == null) {
return of(true);
}
if (typeof (value) === 'string') {
value = value.replace(/\"/g, '\\"');
}
if (limitToValues.length > 0 && !limitToValues.some((v) => v === value)) {
return of(true);
}
let query = `${key}:${value}`;
if (typeof value === 'string') {
query = `${key}:"${value}"`;
}
if (filter) {
const filterFn = Function('model', `return ${filter};`);
query = `${query} AND ` + filterFn(model);
}
if (excludePid) {
query += ` NOT pid:${excludePid}`;
}
return this.getRecords(recordType, query, 1, 1).pipe(map$4((res) => this.totalHits(res.hits.total)), map$4((total) => (total ? { alreadyTaken: value } : null)), debounceTime(500));
}
/**
* Transform a total value string or object representation
* (ES compatibility v6 and v7)
* @param total - string or object
* @param relation - boolean
* @return integer, text or null
*/
totalHits(total, relation = false) {
switch (typeof total) {
case 'object':
if (relation) {
return `${this.translateService.instant(total.relation)} ${total.value.toString()}`;
}
return Number(total.value);
case 'number':
return total;
case 'string':
return Number(total);
default:
return null;
}
}
/**
* Return the suggestions for query and field.
*
* @param resource Resource type.
* @param url URL of suggestions endpoint.
* @param field Field to search for suggestions.
* @param q Query.
* @returns Observable containing the list of suggestions.
*/
suggestions(resource, url, field, q) {
return this.http.get(`${this.apiService.baseUrl}${url}`, {
params: { resource, field, q },
});
}
/**
* Error handling during api call process.
* @param error - HttpErrorResponse
* @param resourceName- Name of current resource
* @return throwError
*/
_handleError(error, resourceName) {
return this.recordHandleErrorService.handleError(error, resourceName);
}
/**
* Creates and returns a HttpHeader object to send to request.
* @param headers Object containing http headers to send to request.
*/
_createRequestHeaders(headers = {}) {
return headers
? new HttpHeaders(headers)
: new HttpHeaders({ 'Content-Type': 'application/json' });
}
/**
* Create a message for event
* @param resource - string
* @param data - any
*/
_createEvent(resource, data) {
return { resource, data };
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Get a record by its PID.
*/
class GetRecordPipe {
constructor() {
this.recordService = inject(RecordService);
}
/**
* Return record data corresponding to PID.
*
* @param pid Record PID.
* @param type Type of the resource.
* @param returnType Type of data to return.
* @param field Field to return.
* @return Record or field record corresponding to PID.
*/
transform(pid, type, returnType = 'object', field, headers) {
// process $ref entrypoint
if (pid.startsWith('http')) {
pid = extractIdOnRef(pid);
}
if ('object' !== returnType) {
returnType = 'field';
}
return this.recordService.getRecord(type, pid, 1, headers).pipe(map$4(data => {
if (!data) {
return null;
}
if ('object' === returnType) {
return data;
}
if (field in data.metadata) {
return data.metadata[field];
}
return null;
}));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GetRecordPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: GetRecordPipe, isStandalone: false, name: "getRecord" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: GetRecordPipe, decorators: [{
type: Pipe,
args: [{
name: 'getRecord',
standalone: false
}]
}] });
const primeNGConfig = {
theme: {
options: {
cssLayer: {
name: 'primeng'
},
darkModeSelector: false
},
preset: definePreset(Aura, {
primitive: {
borderRadius: {
none: "0",
xs: "2px",
sm: "4px",
md: "6px",
lg: "8px",
xl: "12px"
},
emerald: {
50: "#ecfdf5",
100: "#d1fae5",
200: "#a7f3d0",
300: "#6ee7b7",
400: "#34d399",
500: "#10b981",
600: "#059669",
700: "#047857",
800: "#065f46",
900: "#064e3b",
950: "#022c22"
},
green: {
50: "#f0fdf4",
100: "#dcfce7",
200: "#bbf7d0",
300: "#86efac",
400: "#4ade80",
500: "#22c55e",
600: "#16a34a",
700: "#15803d",
800: "#166534",
900: "#14532d",
950: "#052e16"
},
lime: {
50: "#f7fee7",
100: "#ecfccb",
200: "#d9f99d",
300: "#bef264",
400: "#a3e635",
500: "#84cc16",
600: "#65a30d",
700: "#4d7c0f",
800: "#3f6212",
900: "#365314",
950: "#1a2e05"
},
red: {
50: "#fef2f2",
100: "#fee2e2",
200: "#fecaca",
300: "#fca5a5",
400: "#f87171",
500: "#ef4444",
600: "#dc2626",
700: "#b91c1c",
800: "#991b1b",
900: "#7f1d1d",
950: "#450a0a"
},
orange: {
50: "#fff7ed",
100: "#ffedd5",
200: "#fed7aa",
300: "#fdba74",
400: "#fb923c",
500: "#f97316",
600: "#ea580c",
700: "#c2410c",
800: "#9a3412",
900: "#7c2d12",
950: "#431407"
},
amber: {
50: "#fffbeb",
100: "#fef3c7",
200: "#fde68a",
300: "#fcd34d",
400: "#fbbf24",
500: "#f59e0b",
600: "#d97706",
700: "#b45309",
800: "#92400e",
900: "#78350f",
950: "#451a03"
},
yellow: {
50: "#fefce8",
100: "#fef9c3",
200: "#fef08a",
300: "#fde047",
400: "#facc15",
500: "#eab308",
600: "#ca8a04",
700: "#a16207",
800: "#854d0e",
900: "#713f12",
950: "#422006"
},
teal: {
50: "#f0fdfa",
100: "#ccfbf1",
200: "#99f6e4",
300: "#5eead4",
400: "#2dd4bf",
500: "#14b8a6",
600: "#0d9488",
700: "#0f766e",
800: "#115e59",
900: "#134e4a",
950: "#042f2e"
},
cyan: {
50: "#ecfeff",
100: "#cffafe",
200: "#a5f3fc",
300: "#67e8f9",
400: "#22d3ee",
500: "#06b6d4",
600: "#0891b2",
700: "#0e7490",
800: "#155e75",
900: "#164e63",
950: "#083344"
},
sky: {
50: "#f0f9ff",
100: "#e0f2fe",
200: "#bae6fd",
300: "#7dd3fc",
400: "#38bdf8",
500: "#0ea5e9",
600: "#0284c7",
700: "#0369a1",
800: "#075985",
900: "#0c4a6e",
950: "#082f49"
},
blue: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
950: "#172554"
},
indigo: {
50: "#eef2ff",
100: "#e0e7ff",
200: "#c7d2fe",
300: "#a5b4fc",
400: "#818cf8",
500: "#6366f1",
600: "#4f46e5",
700: "#4338ca",
800: "#3730a3",
900: "#312e81",
950: "#1e1b4b"
},
violet: {
50: "#f5f3ff",
100: "#ede9fe",
200: "#ddd6fe",
300: "#c4b5fd",
400: "#a78bfa",
500: "#8b5cf6",
600: "#7c3aed",
700: "#6d28d9",
800: "#5b21b6",
900: "#4c1d95",
950: "#2e1065"
},
purple: {
50: "#faf5ff",
100: "#f3e8ff",
200: "#e9d5ff",
300: "#d8b4fe",
400: "#c084fc",
500: "#a855f7",
600: "#9333ea",
700: "#7e22ce",
800: "#6b21a8",
900: "#581c87",
950: "#3b0764"
},
fuchsia: {
50: "#fdf4ff",
100: "#fae8ff",
200: "#f5d0fe",
300: "#f0abfc",
400: "#e879f9",
500: "#d946ef",
600: "#c026d3",
700: "#a21caf",
800: "#86198f",
900: "#701a75",
950: "#4a044e"
},
pink: {
50: "#fdf2f8",
100: "#fce7f3",
200: "#fbcfe8",
300: "#f9a8d4",
400: "#f472b6",
500: "#ec4899",
600: "#db2777",
700: "#be185d",
800: "#9d174d",
900: "#831843",
950: "#500724"
},
rose: {
50: "#fff1f2",
100: "#ffe4e6",
200: "#fecdd3",
300: "#fda4af",
400: "#fb7185",
500: "#f43f5e",
600: "#e11d48",
700: "#be123c",
800: "#9f1239",
900: "#881337",
950: "#4c0519"
},
slate: {
50: "#f8fafc",
100: "#f1f5f9",
200: "#e2e8f0",
300: "#cbd5e1",
400: "#94a3b8",
500: "#64748b",
600: "#475569",
700: "#334155",
800: "#1e293b",
900: "#0f172a",
950: "#020617"
},
gray: {
50: "#f9fafb",
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
400: "#9ca3af",
500: "#6b7280",
600: "#4b5563",
700: "#374151",
800: "#1f2937",
900: "#111827",
950: "#030712"
},
zinc: {
50: "#fafafa",
100: "#f4f4f5",
200: "#e4e4e7",
300: "#d4d4d8",
400: "#a1a1aa",
500: "#71717a",
600: "#52525b",
700: "#3f3f46",
800: "#27272a",
900: "#18181b",
950: "#09090b"
},
neutral: {
50: "#fafafa",
100: "#f5f5f5",
200: "#e5e5e5",
300: "#d4d4d4",
400: "#a3a3a3",
500: "#737373",
600: "#525252",
700: "#404040",
800: "#262626",
900: "#171717",
950: "#0a0a0a"
},
stone: {
50: "#fafaf9",
100: "#f5f5f4",
200: "#e7e5e4",
300: "#d6d3d1",
400: "#a8a29e",
500: "#78716c",
600: "#57534e",
700: "#44403c",
800: "#292524",
900: "#1c1917",
950: "#0c0a09"
}
},
semantic: {
transitionDuration: "0.2s",
focusRing: {
width: "1px",
style: "solid",
color: "{primary.color}",
offset: "2px",
shadow: "none"
},
disabledOpacity: "0.6",
iconSize: "1rem",
anchorGutter: "2px",
primary: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
950: "#172554"
},
formField: {
paddingX: "0.75rem",
paddingY: "0.5rem",
sm: {
fontSize: "0.875rem",
paddingX: "0.625rem",
paddingY: "0.375rem"
},
lg: {
fontSize: "1.125rem",
paddingX: "0.875rem",
paddingY: "0.625rem"
},
borderRadius: "{border.radius.md}",
focusRing: {
width: "0",
style: "none",
color: "transparent",
offset: "0",
shadow: "none"
},
transitionDuration: "{transition.duration}"
},
list: {
padding: "0.25rem 0.25rem",
gap: "2px",
header: {
padding: "0.5rem 1rem 0.25rem 1rem"
},
option: {
padding: "0.5rem 0.75rem",
borderRadius: "{border.radius.sm}"
},
optionGroup: {
padding: "0.5rem 0.75rem",
fontWeight: "600"
}
},
content: {
borderRadius: "{border.radius.md}"
},
mask: {
transitionDuration: "0.15s"
},
navigation: {
list: {
padding: "0.25rem 0.25rem",
gap: "2px"
},
item: {
padding: "0.5rem 0.75rem",
borderRadius: "{border.radius.sm}",
gap: "0.5rem"
},
submenuLabel: {
padding: "0.5rem 0.75rem",
fontWeight: "600"
},
submenuIcon: {
size: "0.875rem"
}
},
overlay: {
select: {
borderRadius: "{border.radius.md}",
shadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)"
},
popover: {
borderRadius: "{border.radius.md}",
padding: "0.75rem",
shadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)"
},
modal: {
borderRadius: "{border.radius.xl}",
padding: "1.25rem",
shadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)"
},
navigation: {
shadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)"
}
},
colorScheme: {
light: {
surface: {
0: "#ffffff",
50: "#f8fafc",
100: "#f1f5f9",
200: "#e2e8f0",
300: "#cbd5e1",
400: "#94a3b8",
500: "#64748b",
600: "#475569",
700: "#334155",
800: "#1e293b",
900: "#0f172a",
950: "#020617"
},
primary: {
color: "{primary.500}",
contrastColor: "#ffffff",
hoverColor: "{primary.600}",
activeColor: "{primary.700}"
},
highlight: {
background: "{primary.50}",
focusBackground: "{primary.100}",
color: "{primary.700}",
focusColor: "{primary.800}"
},
mask: {
background: "rgba(0,0,0,0.4)",
color: "{surface.200}"
},
formField: {
background: "{surface.0}",
disabledBackground: "{surface.200}",
filledBackground: "{surface.50}",
filledHoverBackground: "{surface.50}",
filledFocusBackground: "{surface.50}",
borderColor: "{surface.300}",
hoverBorderColor: "{surface.400}",
focusBorderColor: "{primary.color}",
invalidBorderColor: "{red.400}",
color: "{surface.700}",
disabledColor: "{surface.500}",
placeholderColor: "{surface.500}",
invalidPlaceholderColor: "{red.600}",
floatLabelColor: "{surface.500}",
floatLabelFocusColor: "{primary.600}",
floatLabelActiveColor: "{surface.500}",
floatLabelInvalidColor: "{form.field.invalid.placeholder.color}",
iconColor: "{surface.400}",
shadow: "0 0 #0000, 0 0 #0000, 0 1px 2px 0 rgba(18, 18, 23, 0.05)"
},
text: {
color: "{surface.800}",
hoverColor: "{surface.900}",
mutedColor: "{surface.500}",
hoverMutedColor: "{surface.600}"
},
content: {
background: "{surface.0}",
hoverBackground: "{surface.100}",
borderColor: "{surface.200}",
color: "{text.color}",
hoverColor: "{text.hover.color}"
},
overlay: {
select: {
background: "{surface.0}",
borderColor: "{surface.200}",
color: "{text.color}"
},
popover: {
background: "{surface.0}",
borderColor: "{surface.200}",
color: "{text.color}"
},
modal: {
background: "{surface.0}",
borderColor: "{surface.200}",
color: "{text.color}"
}
},
list: {
option: {
focusBackground: "{surface.100}",
selectedBackground: "{highlight.background}",
selectedFocusBackground: "{highlight.focus.background}",
color: "{text.color}",
focusColor: "{text.hover.color}",
selectedColor: "{highlight.color}",
selectedFocusColor: "{highlight.focus.color}",
icon: {
color: "{surface.400}",
focusColor: "{surface.500}"
}
},
optionGroup: {
background: "transparent",
color: "{text.muted.color}"
}
},
navigation: {
item: {
focusBackground: "{surface.100}",
activeBackground: "{surface.100}",
color: "{text.color}",
focusColor: "{text.hover.color}",
activeColor: "{text.hover.color}",
icon: {
color: "{surface.400}",
focusColor: "{surface.500}",
activeColor: "{surface.500}"
}
},
submenuLabel: {
background: "transparent",
color: "{text.muted.color}"
},
submenuIcon: {
color: "{surface.400}",
focusColor: "{surface.500}",
activeColor: "{surface.500}"
}
}
},
dark: {
surface: {
0: "#ffffff",
50: "#fafafa",
100: "#f4f4f5",
200: "#e4e4e7",
300: "#d4d4d8",
400: "#a1a1aa",
500: "#71717a",
600: "#52525b",
700: "#3f3f46",
800: "#27272a",
900: "#18181b",
950: "#09090b"
},
primary: {
color: "{primary.400}",
contrastColor: "{surface.900}",
hoverColor: "{primary.300}",
activeColor: "{primary.200}"
},
highlight: {
background: "color-mix(in srgb, {primary.400}, transparent 84%)",
focusBackground: "color-mix(in srgb, {primary.400}, transparent 76%)",
color: "rgba(255,255,255,.87)",
focusColor: "rgba(255,255,255,.87)"
},
mask: {
background: "rgba(0,0,0,0.6)",
color: "{surface.200}"
},
formField: {
background: "{surface.950}",
disabledBackground: "{surface.700}",
filledBackground: "{surface.800}",
filledHoverBackground: "{surface.800}",
filledFocusBackground: "{surface.800}",
borderColor: "{surface.600}",
hoverBorderColor: "{surface.500}",
focusBorderColor: "{primary.color}",
invalidBorderColor: "{red.300}",
color: "{surface.0}",
disabledColor: "{surface.400}",
placeholderColor: "{surface.400}",
invalidPlaceholderColor: "{red.400}",
floatLabelColor: "{surface.400}",
floatLabelFocusColor: "{primary.color}",
floatLabelActiveColor: "{surface.400}",
floatLabelInvalidColor: "{form.field.invalid.placeholder.color}",
iconColor: "{surface.400}",
shadow: "0 0 #0000, 0 0 #0000, 0 1px 2px 0 rgba(18, 18, 23, 0.05)"
},
text: {
color: "{surface.0}",
hoverColor: "{surface.0}",
mutedColor: "{surface.400}",
hoverMutedColor: "{surface.300}"
},
content: {
background: "{surface.900}",
hoverBackground: "{surface.800}",
borderColor: "{surface.700}",
color: "{text.color}",
hoverColor: "{text.hover.color}"
},
overlay: {
select: {
background: "{surface.900}",
borderColor: "{surface.700}",
color: "{text.color}"
},
popover: {
background: "{surface.900}",
borderColor: "{surface.700}",
color: "{text.color}"
},
modal: {
background: "{surface.900}",
borderColor: "{surface.700}",
color: "{text.color}"
}
},
list: {
option: {
focusBackground: "{surface.800}",
selectedBackground: "{highlight.background}",
selectedFocusBackground: "{highlight.focus.background}",
color: "{text.color}",
focusColor: "{text.hover.color}",
selectedColor: "{highlight.color}",
selectedFocusColor: "{highlight.focus.color}",
icon: {
color: "{surface.500}",
focusColor: "{surface.400}"
}
},
optionGroup: {
background: "transparent",
color: "{text.muted.color}"
}
},
navigation: {
item: {
focusBackground: "{surface.800}",
activeBackground: "{surface.800}",
color: "{text.color}",
focusColor: "{text.hover.color}",
activeColor: "{text.hover.color}",
icon: {
color: "{surface.500}",
focusColor: "{surface.400}",
activeColor: "{surface.400}"
}
},
submenuLabel: {
background: "transparent",
color: "{text.muted.color}"
},
submenuIcon: {
color: "{surface.500}",
focusColor: "{surface.400}",
activeColor: "{surface.400}"
}
}
}
}
},
components: {
accordion: {
header: {
borderRadius: "{content.border.radius}",
borderColor: "{content.border.color}",
background: "{surface.100}",
hoverBackground: "{surface.200}",
activeBackground: "{surface.200}",
activeHoverBackground: "{surface.200}"
},
content: {
padding: "0.5rem 0.25rem"
}
},
autocomplete: {
option: {
padding: "0.2rem 0.5rem"
}
},
card: {
root: {
background: "{surface.100}",
},
body: {
padding: "0",
}
},
fieldset: {
legend: {
padding: "0 0.25rem 0.25rem 0.25rem",
}
},
listbox: {
option: {
padding: "0.2rem 0.5rem"
}
},
menubar: {
root: {
borderColor: "transparent",
}
},
panel: {
root: {
background: "{surface.100}",
},
content: {
padding: "0"
}
},
tag: {
root: {
fontSize: "0.8rem",
fontWeight: "600",
padding: "0.15rem 0.5rem",
gap: "0.25rem",
borderRadius: "{content.border.radius}",
roundedBorderRadius: "{border.radius.xl}"
},
icon: {
size: "0.5rem"
},
colorScheme: {
light: {
primary: {
background: "{primary.100}",
color: "{primary.700}"
},
secondary: {
background: "{surface.500}",
color: "{surface.0}"
},
success: {
background: "{green.100}",
color: "{green.700}"
},
info: {
background: "{sky.100}",
color: "{sky.700}"
},
warn: {
background: "{orange.100}",
color: "{orange.700}"
},
danger: {
background: "{red.100}",
color: "{red.700}"
},
contrast: {
background: "{surface.950}",
color: "{surface.0}"
}
},
dark: {
primary: {
background: "color-mix(in srgb, {primary.500}, transparent 84%)",
color: "{primary.300}"
},
secondary: {
background: "{surface.800}",
color: "{surface.300}"
},
success: {
background: "color-mix(in srgb, {green.500}, transparent 84%)",
color: "{green.300}"
},
info: {
background: "color-mix(in srgb, {sky.500}, transparent 84%)",
color: "{sky.300}"
},
warn: {
background: "color-mix(in srgb, {orange.500}, transparent 84%)",
color: "{orange.300}"
},
danger: {
background: "color-mix(in srgb, {red.500}, transparent 84%)",
color: "{red.300}"
},
contrast: {
background: "{surface.0}",
color: "{surface.950}"
}
}
}
},
timeline: {
event: {
minHeight: "2.5rem",
}
}
}
})
}
};
/*
* RERO angular core
* Copyright (C) 2019-2023 RERO
* Copyright (C) 2019-2023 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class DetailButtonComponent {
constructor() {
// Inject
this.location = inject(Location);
/** Record */
this.record = input();
/** Record type */
this.type = input();
/** Admin mode for CRUD operations */
this.adminMode = input({ can: false, message: '' });
/** Record can be used ? */
this.useStatus = input({ can: false, message: '', url: '' });
/** Record can be updated ? */
this.updateStatus = input({ can: false, message: '' });
/** Record can be deleted ? */
this.deleteStatus = input({ can: false, message: '' });
/** Record event */
this.recordEvent = output();
/** Delete record message event */
this.deleteMessageEvent = output();
}
/** Use the record */
useRecord() {
this.recordEvent.emit({ action: 'use', url: this.updateStatus().url });
}
/**
* Update record event
* @param record - the current record
*/
updateRecord(record) {
this.recordEvent.emit({ action: 'update', record });
}
/**
* Delete record event
* @param record - the current record
*/
deleteRecord(record) {
if (this.deleteStatus().can) {
this.recordEvent.emit({ action: 'delete', record });
}
else {
this.deleteMessageEvent.emit(this.deleteStatus().message.replace(new RegExp('\n', 'g'), '<br>'));
}
}
/** Go back to previous page */
goBack() {
this.location.back();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DetailButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: DetailButtonComponent, isStandalone: false, selector: "ng-core-detail-button", inputs: { record: { classPropertyName: "record", publicName: "record", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, useStatus: { classPropertyName: "useStatus", publicName: "useStatus", isSignal: true, isRequired: false, transformFunction: null }, updateStatus: { classPropertyName: "updateStatus", publicName: "updateStatus", isSignal: true, isRequired: false, transformFunction: null }, deleteStatus: { classPropertyName: "deleteStatus", publicName: "deleteStatus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recordEvent: "recordEvent", deleteMessageEvent: "deleteMessageEvent" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div class=\"core:flex core:justify-between core:flex-wrap core:mb-4\">\n <p-button\n id=\"detail-back-button\"\n styleClass=\"core:grow\"\n icon=\"fa fa-arrow-left\"\n [label]=\"'Back'|translate\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"goBack()\"\n />\n <div class=\"core:flex core:flex-wrap core:items-center core:gap-1\">\n <ng-content select=\"[beforeButton]\"></ng-content>\n @if (record() && adminMode().can) {\n @if (useStatus() && useStatus().can && useStatus().url) {\n <p-button\n id=\"detail-use-button\"\n icon=\"fa fa-hand-o-right\"\n [label]=\"'Use'|translate\"\n (onClick)=\"useRecord()\"\n translate\n />\n }\n @if (updateStatus() && updateStatus().can) {\n <p-button\n id=\"detail-edit-button\"\n icon=\"fa fa-pencil\"\n [label]=\"'Edit'|translate\"\n [outlined]=\"useStatus()?.can\"\n (onClick)=\"updateRecord(record())\"\n translate\n />\n }\n @if (deleteStatus()) {\n <p-button\n id=\"detail-delete-button\"\n icon=\"fa fa-trash\"\n severity=\"danger\"\n class=\"core:pointer-events-auto\"\n outlined\n (onClick)=\"deleteRecord(record())\"\n [pTooltip]=\"tooltipContent\"\n tooltipPosition=\"bottom\"\n [disabled]=\"!deleteStatus().can\"\n [label]=\"'Delete'|translate\"\n [tooltipDisabled]=\"deleteStatus().can\"\n />\n <ng-template #tooltipContent>\n <span [innerHTML]=\"deleteStatus().message\"></span>\n </ng-template>\n }\n }\n <ng-content select=\"[afterButton]\"/>\n </div>\n</div>\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "directive", type: i5.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DetailButtonComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-detail-button', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div class=\"core:flex core:justify-between core:flex-wrap core:mb-4\">\n <p-button\n id=\"detail-back-button\"\n styleClass=\"core:grow\"\n icon=\"fa fa-arrow-left\"\n [label]=\"'Back'|translate\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"goBack()\"\n />\n <div class=\"core:flex core:flex-wrap core:items-center core:gap-1\">\n <ng-content select=\"[beforeButton]\"></ng-content>\n @if (record() && adminMode().can) {\n @if (useStatus() && useStatus().can && useStatus().url) {\n <p-button\n id=\"detail-use-button\"\n icon=\"fa fa-hand-o-right\"\n [label]=\"'Use'|translate\"\n (onClick)=\"useRecord()\"\n translate\n />\n }\n @if (updateStatus() && updateStatus().can) {\n <p-button\n id=\"detail-edit-button\"\n icon=\"fa fa-pencil\"\n [label]=\"'Edit'|translate\"\n [outlined]=\"useStatus()?.can\"\n (onClick)=\"updateRecord(record())\"\n translate\n />\n }\n @if (deleteStatus()) {\n <p-button\n id=\"detail-delete-button\"\n icon=\"fa fa-trash\"\n severity=\"danger\"\n class=\"core:pointer-events-auto\"\n outlined\n (onClick)=\"deleteRecord(record())\"\n [pTooltip]=\"tooltipContent\"\n tooltipPosition=\"bottom\"\n [disabled]=\"!deleteStatus().can\"\n [label]=\"'Delete'|translate\"\n [tooltipDisabled]=\"deleteStatus().can\"\n />\n <ng-template #tooltipContent>\n <span [innerHTML]=\"deleteStatus().message\"></span>\n </ng-template>\n }\n }\n <ng-content select=\"[afterButton]\"/>\n </div>\n</div>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const CONFIG = {
MESSAGE_LIFE: 5000, // Message lifetime in ms
DEFAULT_SELECT_SCROLL_HEIGHT: '50vh'
};
/*
* RERO angular core
* Copyright (C) 2019-2024 RERO
* Copyright (C) 2019-2023 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordUiService {
constructor() {
this.translateService = inject(TranslateService);
this.recordService = inject(RecordService);
this.router = inject(Router);
this.confirmationService = inject(ConfirmationService);
this.messageService = inject(MessageService);
/** Configuration for all resources. */
this.types = [];
}
/**
* Delete a record by its PID.
* @param type Type of resource
* @param pid - string, PID to delete
* @returns Observable resolving as a boolean
*/
deleteRecord(type, pid) {
const delete$ = new BehaviorSubject(false);
this.confirmationService.confirm({
acceptLabel: this.translateService.instant('Delete'),
rejectLabel: this.translateService.instant('Cancel'),
message: this.deleteMessage(pid, type).join('<br>'),
header: this.translateService.instant('Confirmation'),
icon: 'fa fa-exclamation-triangle fa-2x core:text-red-500',
acceptButtonStyleClass: "core:bg-red-500 core:border-red-500",
rejectButtonStyleClass: "p-button-text",
accept: () => {
this.recordService.delete(type, pid).subscribe({
next: () => {
delete$.next(true);
this.messageService.add({
severity: 'info',
summary: this.translateService.instant('Confirmed'),
detail: this.translateService.instant('Record deleted.'),
life: CONFIG.MESSAGE_LIFE
});
},
error: (error) => {
delete$.next(false);
this.messageService.add({
severity: 'error',
summary: this.translateService.instant('Error'),
detail: this.translateService.instant(error.title),
sticky: true,
closable: true
});
}
});
}
});
return delete$.asObservable();
}
/**
* Show dialog with the reason why record cannot be deleted.
* @param message Message to display
*/
showDeleteMessage(message) {
this.confirmationService.confirm({
acceptLabel: this.translateService.instant('OK'),
rejectVisible: false,
header: this.translateService.instant('Information'),
message: message,
acceptIcon: "none",
});
}
/**
* Get resource configuration for given type.
*
* @param type Current type to find
* @returns Object configuration for the current type
*/
getResourceConfig(type) {
if (this.types == null) {
throw new Error(`Configuration not found for type "${type}"`);
}
const index = this.types.findIndex(item => item.key === type);
if (index === -1) {
throw new Error(`Configuration not found for type "${type}"`);
}
return this.types[index];
}
/**
* Redirects after saving a resource
* @param pid - string, pid of the record
* @param record - object, record to save
* @param recordType - string, Type of resource
* @param action - string, http action: create or update
* @param route - ActivatedRoute: the current route used
*/
redirectAfterSave(pid, record, recordType, action, route) {
const config = this.getResourceConfig(recordType);
if (config.redirectUrl) {
config.redirectUrl(record, action).subscribe((result) => {
if (result !== null) {
this.router.navigateByUrl(result);
return;
}
});
}
else {
// Default behavior
if (action === 'update') {
this.router.navigate(['../../detail', pid], { relativeTo: route, replaceUrl: true });
return;
}
this.router.navigate(['../detail', pid], { relativeTo: route, replaceUrl: true });
}
}
/**
* Personalized message for delete a record
* @param pid - record pid string
* @param type - Type of resource
* @returns Observable array of string
*/
deleteMessage(pid, type) {
const defaultMessage = [
this.translateService.instant('Do you really want to delete this record?')
];
try {
const config = this.getResourceConfig(type);
return (config.deleteMessage)
? config.deleteMessage(pid)
: defaultMessage;
}
catch {
return defaultMessage;
}
}
// ================================================================
// Permissions
// ================================================================
/**
* Check if a record can be added
* @param type Type of resource
* @returns Observable resolving an object containing the result of a permission check.
*/
canAddRecord$(type) {
const config = this.getResourceConfig(type);
return (config.canAdd)
? config.canAdd().pipe(first())
: of({ can: true, message: '' });
}
/**
* Check if a record can be updated
* @param record - object, record to check
* @param type Type of resource
* @returns Observable resolving an object containing the result of a permission check.
*/
canUpdateRecord$(record, type) {
const config = this.getResourceConfig(type);
// The canUpdate function takes precedence over permissions
if (config.canUpdate) {
return config.canUpdate(record).pipe(first());
}
if (config.permissions) {
const permissions = config.permissions(record);
return permissions.pipe(map$4((perms) => {
if ('canUpdate' in perms) {
return perms.canUpdate;
}
else {
return of({ can: true, message: '' });
}
}));
}
return of({ can: true, message: '' });
}
/**
* Check if a record can be deleted
* @param record - object, record to check
* @param type Type of resource
* @returns Observable resolving an object containing the result of a permission check.
*/
canDeleteRecord$(record, type) {
const config = this.getResourceConfig(type);
// The canDelete function takes precedence over permissions
if (config.canDelete) {
return config.canDelete(record).pipe(first());
}
if (config.permissions) {
const permissions = config.permissions(record);
return permissions.pipe(map$4((perms) => {
if ('canDelete' in perms) {
return perms.canDelete;
}
else {
return of({ can: true, message: '' });
}
}));
}
return of({ can: true, message: '' });
}
/**
* Check if a record can be read
* @param record - object, record to check
* @param type Type of resource
* @returns Observable resolving an object containing the result of a permission check.
*/
canReadRecord$(record, type) {
const config = this.getResourceConfig(type);
// The canRead function takes precedence over permissions
if (config.canRead) {
return config.canRead(record).pipe(first());
}
if (config.permissions) {
const permissions = config.permissions(record);
return permissions.pipe(map$4((perms) => {
if ('canRead' in perms) {
return perms.canRead;
}
else {
return of({ can: true, message: '' });
}
}));
}
return of({ can: true, message: '' });
}
/**
* Check if a record can be used (mainly used for templates)
* @param record - object, record to check
* @param type Type of resource
* @returns Observable resolving an object containing the result of a permission check.
*/
canUseRecord$(record, type) {
const config = this.getResourceConfig(type);
// The canUse function takes precedence over permissions
if (config.canUse) {
return config.canUse(record).pipe(first());
}
if (config.permissions) {
const permissions = config.permissions(record);
if ('canUse' in permissions) {
return permissions.canUse;
}
}
return of({ can: false, message: '' });
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordUiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordUiService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordUiService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Directive for displaying a record detail.
*/
class RecordDetailDirective {
constructor() {
this.viewContainerRef = inject(ViewContainerRef);
/**
* Record to display
*/
this.record = {};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordDetailDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.14", type: RecordDetailDirective, isStandalone: false, selector: "[ngCoreRecordDetail]", inputs: { record: "record", type: "type" }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordDetailDirective, decorators: [{
type: Directive,
args: [{
selector: '[ngCoreRecordDetail]',
standalone: false
}]
}], propDecorators: { record: [{
type: Input
}], type: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component to display a record by dumping its data to JSON.
*/
let JsonComponent$1 = class JsonComponent {
/**
* Component initialization.
*
* Subscribes to observable for getting the record.
*/
ngOnInit() {
this.record$.subscribe((record) => {
this.record = record;
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JsonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: JsonComponent, isStandalone: false, selector: "ng-component", ngImport: i0, template: `
@if (record) {
<h1>Record of type "{{ type }}" #{{ record.id }}</h1>
<pre>
<div style="white-space: pre-wrap;">{{ record|json }}</div>
</pre>
}
`, isInline: true, dependencies: [{ kind: "pipe", type: i1$2.JsonPipe, name: "json" }] }); }
};
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JsonComponent$1, decorators: [{
type: Component,
args: [{
template: `
@if (record) {
<h1>Record of type "{{ type }}" #{{ record.id }}</h1>
<pre>
<div style="white-space: pre-wrap;">{{ record|json }}</div>
</pre>
}
`,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2023 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class DetailComponent {
constructor() {
this.route = inject(ActivatedRoute);
this.router = inject(Router);
this.location = inject(Location);
this.recordService = inject(RecordService);
this.recordUiService = inject(RecordUiService);
this.translate = inject(TranslateService);
this.spinner = inject(NgxSpinnerService);
this.messageService = inject(MessageService);
this.viewContainerRef = inject(ViewContainerRef);
/** View component for displaying record */
this.viewComponent = null;
/** Record can be used ? */
this.useStatus = { can: false, message: '', url: '' };
/** Record can be updated ? */
this.updateStatus = { can: false, message: '' };
/** Record can be deleted ? */
this.deleteStatus = { can: false, message: '' };
/** Observable resolving record data */
this.record$ = null;
/** Record data */
this.record = null;
/** Admin mode for CRUD operations */
this.adminMode = { can: true, message: '' };
/** Object type route config */
this.config = null;
}
/** On init hook */
ngOnInit() {
this.routeParametersSubscription = this.route.paramMap.subscribe(() => {
this.spinner.show();
this.loadViewComponentRef();
const pid = this.route.snapshot.paramMap.get('pid');
this.type = this.route.snapshot.paramMap.get('type');
this.recordUiService.types = this.route.snapshot.data.types;
this.config = this.recordUiService.getResourceConfig(this.type);
const type = this.config.index || this.config.key;
this.record$ = this.recordService.getRecord(type, pid, 1, this.config.itemHeaders || null).pipe(catchError$1((error) => {
this.error = error;
this.spinner.hide();
return of(null);
}), tap$1(record => {
this.record = record;
this.recordUiService.canReadRecord$(this.record, this.type).subscribe(result => {
if (result.can === false) {
this.messageService.add({
severity: 'error',
summary: this.translate.instant(this.type),
detail: this.translate.instant('You cannot read this record'),
sticky: true,
closable: true
});
this.location.back();
}
});
this.recordUiService.canDeleteRecord$(this.record, this.type).subscribe(result => {
this.deleteStatus = result;
});
this.recordUiService.canUpdateRecord$(this.record, this.type).subscribe(result => {
this.updateStatus = result;
});
this.recordUiService.canUseRecord$(this.record, this.type).subscribe(result => {
this.useStatus = result;
});
if (this.route.snapshot.data.adminMode) {
this.route.snapshot.data.adminMode().subscribe((am) => this.adminMode = am);
}
this.spinner.hide();
}));
this.loadRecordView();
});
}
/**
* Component destruction.
*
* Unsubscribes from the observables of the route parameters.
* Unsubscribes from the observable for detail view.
*/
ngOnDestroy() {
this.routeParametersSubscription.unsubscribe();
if (this.detailComponentSubscription) {
this.detailComponentSubscription.unsubscribe();
}
}
/**
* Record event
* @param event - Record event message
*/
recordEvent(event) {
switch (event.action) {
case 'use':
this.router.navigateByUrl(event.url);
break;
case 'update':
this.router.navigate(['../../edit', event.record.id], { relativeTo: this.route, replaceUrl: true });
break;
case 'delete':
this.deleteRecord(event.record);
break;
default:
throw new Error('Missing record event action.');
}
}
/**
* Getter giving the information if file management is enabled.
* @returns True if file management is enabled.
*/
get filesEnabled() {
return this.config.files && this.config.files.enabled ? this.config.files.enabled : false;
}
/**
* Delete the record and go back to previous page.
* @param event - DOM event
* @param element - string (PID to remove) or object
*/
deleteRecord(element) {
const pid = typeof element === 'object' ? element.id : element;
return this.recordUiService.deleteRecord(this.type, pid).subscribe((result) => {
if (result === true) {
let redirectUrl = '../..';
const navigateOptions = { relativeTo: this.route };
if (typeof element === 'object' && this.config.redirectUrl) {
this.config.redirectUrl(element, 'delete').subscribe((redirect) => {
if (redirect !== null) {
redirectUrl = redirect;
}
this.router.navigate([redirectUrl], navigateOptions);
});
}
this.router.navigate([redirectUrl], navigateOptions);
}
});
}
/**
* Show a modal containing message given in parameter.
* @param message - message to display into modal
*/
showDeleteMessage(message) {
this.recordUiService.showDeleteMessage(message);
}
/** Dynamically load component depending on selected resource type. */
loadRecordView() {
this.viewContainerRef.clear();
const componentRef = this.viewContainerRef.createComponent(this.viewComponent || JsonComponent$1);
componentRef.instance.record$ = this.record$;
componentRef.instance.type = this.route.snapshot.paramMap.get('type');
}
/** Load component view corresponding to type */
loadViewComponentRef() {
if (!this.route.snapshot.data.types || this.route.snapshot.data.types.length === 0) {
throw new Error('Configuration types not passed to component');
}
const type = this.route.snapshot.paramMap.get('type');
const { types } = this.route.snapshot.data;
const index = types.findIndex((item) => item.key === type);
if (index === -1) {
throw new Error(`Configuration not found for type "${type}"`);
}
if (types[index].detailComponent) {
let detailComponent$ = types[index].detailComponent;
if (isObservable(detailComponent$) === false) {
detailComponent$ = of(detailComponent$);
}
this.detailComponentSubscription = detailComponent$.subscribe((component) => {
this.viewComponent = component;
});
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DetailComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: DetailComponent, isStandalone: false, selector: "ng-core-record-detail", inputs: { viewComponent: "viewComponent" }, viewQueries: [{ propertyName: "recordDetail", first: true, predicate: RecordDetailDirective, descendants: true, static: true }], ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div>\n @if (record) {\n <ng-core-detail-button\n [record]=\"record\"\n [type]=\"type\"\n [adminMode]=\"adminMode\"\n [useStatus]=\"useStatus\"\n [updateStatus]=\"updateStatus\"\n [deleteStatus]=\"deleteStatus\"\n (recordEvent)=\"recordEvent($event)\"\n (deleteMessageEvent)=\"showDeleteMessage($event)\"\n />\n }\n @if (error) {\n <ng-core-error [error]=\"error\"/>\n }\n <ng-template ngCoreRecordDetail/>\n</div>\n", dependencies: [{ kind: "component", type: ErrorComponent, selector: "ng-core-error", inputs: ["error"] }, { kind: "directive", type: RecordDetailDirective, selector: "[ngCoreRecordDetail]", inputs: ["record", "type"] }, { kind: "component", type: DetailButtonComponent, selector: "ng-core-detail-button", inputs: ["record", "type", "adminMode", "useStatus", "updateStatus", "deleteStatus"], outputs: ["recordEvent", "deleteMessageEvent"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DetailComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-record-detail', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div>\n @if (record) {\n <ng-core-detail-button\n [record]=\"record\"\n [type]=\"type\"\n [adminMode]=\"adminMode\"\n [useStatus]=\"useStatus\"\n [updateStatus]=\"updateStatus\"\n [deleteStatus]=\"deleteStatus\"\n (recordEvent)=\"recordEvent($event)\"\n (deleteMessageEvent)=\"showDeleteMessage($event)\"\n />\n }\n @if (error) {\n <ng-core-error [error]=\"error\"/>\n }\n <ng-template ngCoreRecordDetail/>\n</div>\n" }]
}], propDecorators: { viewComponent: [{
type: Input
}], recordDetail: [{
type: ViewChild,
args: [RecordDetailDirective, { static: true }]
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RouteCollectionService {
constructor() {
/**
* Collection of routes
*/
this.collection = new Map();
}
/**
* Add route on collection
* @param route - RouteInterface
* @return this - RouteCollectionService
*/
addRoute(route) {
const routeName = route.name;
if (!this.hasRoute(routeName)) {
this.collection.set(routeName, route);
}
return this;
}
/**
* Delete route on collection
* @param name - string
* @return boolean
*/
deleteRoute(name) {
if (this.hasRoute(name)) {
this.collection.delete(name);
return true;
}
return false;
}
/**
* Check if route is present to the collection
* @param name - string
* @return boolean
*/
hasRoute(name) {
return this.collection.has(name);
}
/**
* Get route by name
* @param name - string
* @return mixed - RouteInterface | null
*/
getRoute(name) {
if (this.hasRoute(name)) {
return this.collection.get(name);
}
return null;
}
/**
* Get All routes
* @return routes separated by comma
*/
getRoutes() {
const routes = [];
for (const value of this.collection.values()) {
routes.push(value.getConfiguration());
}
return routes;
}
/**
* Return a array of route name
* @return array
*/
availableRoutesName() {
const routesName = [];
for (const route of this.collection.keys()) {
routesName.push(route);
}
return routesName;
}
/**
* Count available routes
* @return number;
*/
size() {
return this.collection.size;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RouteCollectionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RouteCollectionService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RouteCollectionService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2022 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class LoggerService {
info(data, message) {
console.log(message, data);
}
warning(data, message) {
console.warn(message, data);
}
error(data, message) {
console.error(message, data);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LoggerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LoggerService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LoggerService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class JSONSchemaService {
constructor() {
this.translateService = inject(TranslateService);
this.recordService = inject(RecordService);
this.apiService = inject(ApiService);
// list of custom validators
this.customValidators = [
'valueAlreadyExists',
'uniqueValueKeysInObject',
'numberOfSpecificValuesInObject',
'dateMustBeGreaterThan',
'dateMustBeLessThan'
];
}
processField(field, jsonSchema) {
// initial population of arrays with a minItems constraints
if (field.type === 'array' && jsonSchema.minItems && !jsonSchema.hasOwnProperty('default')) {
field.defaultValue = new Array(jsonSchema.minItems);
}
// If 'format' is defined into the jsonSchema, use it as props to try a validation on this field.
// See: `email.validator.ts` file
if (jsonSchema.format) {
field.props.type = jsonSchema.format;
}
if (jsonSchema?.widget?.formlyConfig) {
const { props } = jsonSchema.widget.formlyConfig;
if (props) {
this.setSimpleOptions(field, props);
this.setValidation(field, props);
this.setRemoteSelectOptions(field, props);
}
}
return field;
}
/**
* Populate a select options with a remote API call.
* @param field formly field config
* @param formOptions JSONSchema object
*/
setRemoteSelectOptions(field, formOptions) {
if (formOptions.remoteOptions && formOptions.remoteOptions.type) {
field.type = 'select';
field.hooks = {
...field.hooks,
afterContentInit: (f) => {
const recordType = formOptions.remoteOptions.type;
const query = formOptions.remoteOptions.query || '';
f.props.options = this.recordService
.getRecords(recordType, query, 1, RecordService.MAX_REST_RESULTS_SIZE)
.pipe(map$5((data) => data.hits.hits.map((record) => {
return {
label: formOptions.remoteOptions.labelField && formOptions.remoteOptions.labelField in record.metadata
? record.metadata[formOptions.remoteOptions.labelField]
: record.metadata.name,
value: this.apiService.getRefEndpoint(recordType, record.id)
};
})));
}
};
}
}
/**
*
* @param field formly field config
* @param formOptions JSONSchema object
*/
setValidation(field, formOptions) {
if (formOptions.validation) {
// custom validation messages
// TODO: use widget instead
const { messages } = formOptions.validation;
if (messages) {
if (!field.validation) {
field.validation = {};
}
if (!field.validation.messages) {
field.validation.messages = {};
}
for (const key of Object.keys(messages)) {
const msg = messages[key];
// add support of key with or without Message suffix (required == requiredMessage),
// this is useful for backend translation extraction
field.validation.messages[key.replace(/Message$/, '')] = (error, f) =>
// translate the validation messages coming from the JSONSchema
// TODO: need to remove `as any` once it is fixed in ngx-formly v.5.7.2
this.translateService.stream(msg);
}
}
// store the custom validators config
field.props.customValidators = {};
if (formOptions.validation && formOptions.validation.validators) {
for (const customValidator of this.customValidators) {
const validatorConfig = formOptions.validation.validators[customValidator];
if (validatorConfig != null) {
field.props.customValidators[customValidator] = validatorConfig;
}
}
}
if (formOptions.validation.validators) {
// validators: add validator with expressions
// TODO: use widget
const validatorsKey = Object.keys(formOptions.validation.validators);
validatorsKey.map(validatorKey => {
const validator = formOptions.validation.validators[validatorKey];
if ('expression' in validator && 'message' in validator) {
const { expression } = validator;
const expressionFn = Function('formControl', `return ${expression};`);
const validatorExpression = {
expression: (fc) => expressionFn(fc),
// translate the validation message coming form the JSONSchema
message: this.translateService.stream(validator.message)
};
field.validators = field.validators !== undefined ? field.validators : {};
field.validators[validatorKey] = validatorExpression;
}
});
}
}
}
/**
* Convert JSONSchema form options to formly field options.
* @param field formly field config
* @param formOptions JSONSchema object
*/
setSimpleOptions(field, formOptions) {
// some fields should not submit the form when enter key is pressed
if (field.props.doNotSubmitOnEnter != null) {
field.props.keydown = (f, event) => {
if (event.key === 'Enter') {
event.preventDefault();
}
};
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JSONSchemaService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JSONSchemaService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JSONSchemaService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TemplatesService {
constructor() {
this.recordService = inject(RecordService);
}
/**
* Get all templates corresponding to a resource type
* @param templateResourceType - string: the resource name where to find templates
* @param resourceType - string: the resource type
* @return an observable of template record
*/
getTemplates(templateResourceType, resourceType) {
const query = (resourceType !== undefined)
? `template_type:${resourceType}`
: '';
return this.recordService.getRecords(templateResourceType, query, 1, RecordService.MAX_REST_RESULTS_SIZE).pipe(map$4((data) => data.hits), map$4(hits => hits.total === 0 ? [] : hits.hits), map$4(hits => hits.map(hit => hit.metadata)));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TemplatesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TemplatesService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TemplatesService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
* Copyright (C) 2020-2023 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class LoadTemplateFormComponent {
constructor() {
this.dynamicDialogRef = inject(DynamicDialogRef);
this.dynamicDialogConfig = inject(DynamicDialogConfig);
this.recordService = inject(RecordService);
this.templateService = inject(TemplatesService);
this.translateService = inject(TranslateService);
this.router = inject(Router);
this.messageService = inject(MessageService);
/** form group */
this.form = new UntypedFormGroup({});
/** fields config of the form */
this.formFields = [];
this.isDataFormLoaded = false;
this.isVisible = true;
}
ngOnInit() {
const { data } = this.dynamicDialogConfig;
this.formFields.push({
key: 'template',
type: 'select',
props: {
appendTo: 'body',
required: true,
group: true,
filter: true,
scrollHeight: CONFIG.DEFAULT_SELECT_SCROLL_HEIGHT,
options: [],
attributes: {
size: 10
}
}
});
this.templateService.getTemplates(data.templateResourceType, data.resourceType)
.subscribe((templates) => {
const items = [];
// private first
const privateItems = templates.filter(template => template.visibility === 'private');
if (privateItems?.length > 0) {
items.push({
label: this.translateService.instant('My private templates'),
untranslatedLabel: 'My private templates',
items: privateItems.map(item => ({
label: item.name,
value: item.pid
}))
});
}
// public last
const publicItems = templates.filter(template => template.visibility === 'public');
if (publicItems?.length > 0) {
items.push({
label: this.translateService.instant('Public templates'),
untranslatedLabel: 'Public templates',
items: publicItems.map(item => ({
label: item.name,
value: item.pid
}))
});
}
this.formFields[0].props.options = items;
this.isDataFormLoaded = true;
});
}
/**
* Submit form hook.
* Redirect to the same editor form url with query params related to the selected template.
*/
onSubmitForm() {
const formValues = this.form.value;
this.recordService.getRecord('templates', formValues.template).subscribe({
next: (template) => {
this.router.navigate([], {
queryParams: {
source: 'templates',
pid: template.id
},
queryParamsHandling: 'merge',
skipLocationChange: true
});
this.closeDialog();
},
error: (error) => {
this.messageService.add({
severity: 'error',
summary: this.translateService.instant('Error'),
detail: error.message,
sticky: true,
closable: true
});
}
});
}
closeDialog() {
this.dynamicDialogRef.close();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LoadTemplateFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: LoadTemplateFormComponent, isStandalone: false, selector: "ng-core-load-template-form", ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n Copyright (C) 2020 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (isDataFormLoaded) {\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmitForm()\">\n <p-message severity=\"warn\">\n <div class=\"core:w-full\">\n <h4 class=\"text-error\" translate>Warning!</h4>\n <p class=\"core:mt-1\" translate>\n Loading a template will reload the current form. All values previously encoded will be lost.\n </p>\n </div>\n </p-message>\n\n <div class=\"core:my-1 core:font-bold\" translate>Select a template</div>\n <formly-form [form]=\"form\" [fields]=\"formFields\" [model]=\"model\" />\n <div class=\"core:flex core:justify-end core:gap-2 core:mt-2\">\n <p-button [label]=\"'Cancel'|translate\" [outlined]=\"true\" severity=\"danger\" (onClick)=\"closeDialog()\" />\n <p-button type=\"submit\" [label]=\"'Load'|translate\" [disabled]=\"this.form.status !== 'VALID'\" />\n </div>\n </form>\n}\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$1.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "directive", type: i5.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i3$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i4$1.LegacyFormlyForm, selector: "formly-form" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LoadTemplateFormComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-load-template-form', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n Copyright (C) 2020 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (isDataFormLoaded) {\n <form [formGroup]=\"form\" (ngSubmit)=\"onSubmitForm()\">\n <p-message severity=\"warn\">\n <div class=\"core:w-full\">\n <h4 class=\"text-error\" translate>Warning!</h4>\n <p class=\"core:mt-1\" translate>\n Loading a template will reload the current form. All values previously encoded will be lost.\n </p>\n </div>\n </p-message>\n\n <div class=\"core:my-1 core:font-bold\" translate>Select a template</div>\n <formly-form [form]=\"form\" [fields]=\"formFields\" [model]=\"model\" />\n <div class=\"core:flex core:justify-end core:gap-2 core:mt-2\">\n <p-button [label]=\"'Cancel'|translate\" [outlined]=\"true\" severity=\"danger\" (onClick)=\"closeDialog()\" />\n <p-button type=\"submit\" [label]=\"'Load'|translate\" [disabled]=\"this.form.status !== 'VALID'\" />\n </div>\n </form>\n}\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
* Copyright (C) 2020-2023 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class SaveTemplateFormComponent {
constructor() {
this.dynamicDialogRef = inject(DynamicDialogRef);
this.translateService = inject(TranslateService);
/** form group */
this.form = new UntypedFormGroup({});
/** fields config of the form */
this.formFields = [];
}
ngOnInit() {
this.formFields.push({
key: 'name',
type: 'string',
props: {
required: true,
placeholder: this.translateService.instant('Choose a name for your template'),
minLength: 3,
}
});
}
/**
* Submit the form
*/
onSubmitForm() {
this.closeDialog(this.form.value);
}
/**
* Close the modal dialog box
*/
closeDialog(data) {
this.dynamicDialogRef.close(data);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SaveTemplateFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SaveTemplateFormComponent, isStandalone: false, selector: "ng-core-save-template-form", ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n Copyright (C) 2020 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n\n<form class=\"core:w-full\" [formGroup]=\"form\" (ngSubmit)=\"onSubmitForm()\">\n <formly-form class=\"core:w-full\" [form]=\"form\" [fields]=\"formFields\" [model]=\"model\"/>\n <div class=\"core:flex core:justify-end core:gap-1 core:mt-2\">\n <p-button [label]=\"'Cancel'|translate\" [outlined]=\"true\" severity=\"danger\" (onClick)=\"closeDialog()\" />\n <p-button type=\"submit\" [label]=\"'Save'|translate\" [disabled]=\"this.form.status !== 'VALID'\" />\n </div>\n</form>\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i4$1.LegacyFormlyForm, selector: "formly-form" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SaveTemplateFormComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-save-template-form', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n Copyright (C) 2020 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n\n<form class=\"core:w-full\" [formGroup]=\"form\" (ngSubmit)=\"onSubmitForm()\">\n <formly-form class=\"core:w-full\" [form]=\"form\" [fields]=\"formFields\" [model]=\"model\"/>\n <div class=\"core:flex core:justify-end core:gap-1 core:mt-2\">\n <p-button [label]=\"'Cancel'|translate\" [outlined]=\"true\" severity=\"danger\" (onClick)=\"closeDialog()\" />\n <p-button type=\"submit\" [label]=\"'Save'|translate\" [disabled]=\"this.form.status !== 'VALID'\" />\n </div>\n</form>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* For big editor add the possibility to display
*/
class AddFieldEditorComponent {
constructor() {
this.translateService = inject(TranslateService);
this.items = signal([]);
// Subscriptions to observables.
this.subscriptions = new Subscription();
this.essentialsOptions = computed(() => this.items()
.filter(f => this.isFieldEssential(f)));
}
onAddField(event) {
this.editorComponentInstance.setHide(event.value, false, true);
}
/** onInit hook */
ngOnInit() {
this.editorComponentInstance = (this.editorComponent)();
this.subscriptions.add(this.editorComponentInstance.hiddenFields$.pipe(tap((fields) => this.fieldsTranslate(fields))).subscribe());
this.subscriptions.add(this.translateService.onLangChange.pipe(tap(() => this.fieldsTranslate(this.items()))).subscribe());
}
/** onDestroy hook */
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
search(event) {
this.suggestions = this.items().filter((item) => {
return item.props.label.toLowerCase().indexOf(event.query.toLowerCase()) > -1;
});
}
onSelect(event) {
this.editorComponentInstance.setHide(event.value, false);
this.autocomplete.clear();
}
/**
* Filter fields to display only essential ones
* @param field - field to check
*/
isFieldEssential(field) {
return field.props?.navigation && field.props.navigation.essential === true;
}
fieldsTranslate(fields) {
this.items.set(fields
.map((field) => {
field.props.label = this.translateService.instant(field.props.untranslatedLabel);
return field;
})
.sort((field1, field2) => field1.props.label.localeCompare(field2.props.label)));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AddFieldEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: AddFieldEditorComponent, isStandalone: false, selector: "ng-core-editor-add-field-editor", inputs: { editorComponent: "editorComponent" }, viewQueries: [{ propertyName: "autocomplete", first: true, predicate: ["addField"], descendants: true }], ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n <div>\n <p-autoComplete\n [dropdown]=\"true\"\n #addField\n optionLabel=\"props.label\"\n [placeholder]=\"'Add field' | translate\"\n styleClass=\"core:mb-2 core:w-full\"\n [suggestions]=\"suggestions\"\n (completeMethod)=\"search($event)\"\n (onSelect)=\"onSelect($event)\"\n [disabled]=\"items().length < 1\"\n scrollHeight=\"400px\"\n appendTo=\"body\"\n />\n </div>\n\n@if(essentialsOptions()?.length > 0) {\n <p-listbox\n [options]=\"essentialsOptions()\"\n scrollHeight=\"\"\n styleClass=\"core:mt-2\"\n (onChange)=\"onAddField($event)\"\n >\n <ng-template #item let-item>\n {{ item.props.label }}\n </ng-template>\n </p-listbox>\n}\n", dependencies: [{ kind: "component", type: i1$3.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "delay", "style", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "disabled", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "maxlength", "name", "required", "size", "appendTo", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "field", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "itemSize", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "variant", "fluid"], outputs: ["completeMethod", "onSelect", "onUnselect", "onFocus", "onBlur", "onDropdownClick", "onClear", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: i2$2.Listbox, selector: "p-listbox, p-listBox, p-list-box", inputs: ["id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "ariaLabel", "selectOnFocus", "searchLocale", "focusOnHover", "filterMessage", "filterFields", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "scrollHeight", "tabindex", "multiple", "style", "styleClass", "listStyle", "listStyleClass", "readonly", "disabled", "checkbox", "filter", "filterBy", "filterMatchMode", "filterLocale", "metaKeySelection", "dataKey", "showToggleAll", "optionLabel", "optionValue", "optionGroupChildren", "optionGroupLabel", "optionDisabled", "ariaFilterLabel", "filterPlaceHolder", "emptyFilterMessage", "emptyMessage", "group", "options", "filterValue", "selectAll", "striped", "highlightOnSelect", "checkmark", "dragdrop"], outputs: ["onChange", "onClick", "onDblClick", "onFilter", "onFocus", "onBlur", "onSelectAllChange", "onLazyLoad", "onDrop"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AddFieldEditorComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-editor-add-field-editor', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n <div>\n <p-autoComplete\n [dropdown]=\"true\"\n #addField\n optionLabel=\"props.label\"\n [placeholder]=\"'Add field' | translate\"\n styleClass=\"core:mb-2 core:w-full\"\n [suggestions]=\"suggestions\"\n (completeMethod)=\"search($event)\"\n (onSelect)=\"onSelect($event)\"\n [disabled]=\"items().length < 1\"\n scrollHeight=\"400px\"\n appendTo=\"body\"\n />\n </div>\n\n@if(essentialsOptions()?.length > 0) {\n <p-listbox\n [options]=\"essentialsOptions()\"\n scrollHeight=\"\"\n styleClass=\"core:mt-2\"\n (onChange)=\"onAddField($event)\"\n >\n <ng-template #item let-item>\n {{ item.props.label }}\n </ng-template>\n </p-listbox>\n}\n" }]
}], propDecorators: { editorComponent: [{
type: Input
}], autocomplete: [{
type: ViewChild,
args: ["addField"]
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class EditorComponent extends AbstractCanDeactivateComponent {
// current list of hidden fields
get hiddenFields$() {
return this._hiddenFieldsSubject.asObservable();
}
// Editor long mode
get longMode() {
return this.editorSettings.longMode;
}
// Editor function
get editorComponent() {
return () => this;
}
/** Constructor */
constructor() {
super();
this.formlyJsonschema = inject(FormlyJsonschema);
this.recordService = inject(RecordService);
this.apiService = inject(ApiService);
this.route = inject(ActivatedRoute);
this.recordUiService = inject(RecordUiService);
this.translateService = inject(TranslateService);
this.location = inject(Location);
this.routeCollectionService = inject(RouteCollectionService);
this.loggerService = inject(LoggerService);
this.jsonschemaService = inject(JSONSchemaService);
this.dialogService = inject(DialogService);
this.messageService = inject(MessageService);
// form initial values
this.model = null;
// initial values changes notification
this.modelChange = new EventEmitter();
// editor loading state notifications
this.loadingChange = new EventEmitter();
// editor can deactivate change notification
this.canDeactivateChange = new EventEmitter();
// Can Deactivate
this.canDeactivate = false;
// editor settings
this.editorSettings = {
longMode: false, // editor long mode
template: {
recordType: undefined, // the resource considerate as template
loadFromTemplate: false, // enable load from template button
saveAsTemplate: false // allow to save the record as a template
}
};
// store pid on edit mode
this.pid = null;
// save alternatives
this.saveAlternatives = [];
// current record type from the url
this.recordType = null;
/** Disables the save button during the action */
this.isSaveButtonDisabled = false;
// subscribers
this._subscribers = new Subscription();
// list of fields to be hidden
this._hiddenFields = [];
// Observable of hidden fields
this._hiddenFieldsSubject = new BehaviorSubject([]);
this.form = new UntypedFormGroup({});
}
/**
* Component has changed
* Called before ngOnInit() and whenever one of the input properties change.
* @param changes: the changed properties
*/
ngOnChanges(changes) {
if (changes.model && !changes.model.isFirstChange()) { // Model has changed
this._setModel(changes.model.currentValue);
// needed to select the right value for existing data in the multi select
// components such as oneOf
if (this.schema) {
this.setSchema(this.schema);
}
}
if (changes.schema && changes.schema.isFirstChange()) { // Schema has changed
this.setSchema(changes.schema.currentValue);
}
if (changes.pid) { // Assign pid on schema. Reload schema
this.pid = changes.pid.currentValue;
if (this.schema) {
this.setSchema(this.schema);
}
}
}
/** onInit hook */
ngOnInit() {
this.tocFields$ = this.hiddenFields$.pipe(debounceTime(300), switchMap(() => {
if (this.fields && this.fields.length > 0) {
return of(this.fields[0].fieldGroup.filter(f => {
// hidden properties should not be in the navigation
if (f.hide === true) {
return false;
}
// properties with hide wrapper should not be in the navigation
if (f.props.alwaysHidden) {
return false;
}
return true;
}));
}
return of([]);
}));
combineLatest([this.route.params, this.route.queryParams]).subscribe(([params, queryParams]) => {
// uncomment for debug
// this.form.valueChanges.subscribe(v =>
// console.log('model', this.model, 'v', v, 'form', this.form, 'field', this.fields)
// );
this.loadingChange.emit(true);
if (!params.type) {
this.model = {};
this.schema = {};
return;
}
this.recordType = params.type;
this.recordUiService.types = this.route.snapshot.data.types;
this._resourceConfig = this.recordUiService.getResourceConfig(this.recordType);
if (this._resourceConfig.editorSettings) {
this.editorSettings = { ...cloneDeep(this.editorSettings), ...this._resourceConfig.editorSettings };
}
// load template resource configuration if needed
// If editor allowed to use a resource as a template, we need to load the configuration
// of this resource and save it into `recordUIService.types` to use it when loading and saving
if (this.editorSettings.template.recordType !== undefined) {
const tmplResource = this.routeCollectionService.getRoute(this.editorSettings.template.recordType);
const tmplConfiguration = tmplResource.getConfiguration();
if (tmplConfiguration.hasOwnProperty('data')
&& tmplConfiguration.data.hasOwnProperty('types')) {
this.recordUiService.types = this.recordUiService.types.concat(tmplConfiguration.data.types);
}
}
// saveAlternatives construction
// Depending of editor configuration, it's possible to provide some alternatives save
// methods. Each method must defined a label and a callback function used when user choose
// this save method.
this.saveAlternatives = [];
if (this.editorSettings.template.saveAsTemplate) {
this.saveAlternatives.push({
label: this.translateService.instant('Save as template') + '…',
command: () => this.saveAsTemplate()
});
}
this.pid = params.pid;
const schema$ = this.recordService.getSchemaForm(this.recordType);
let record$ = of({ record: {}, result: null });
// load from template
// If the url contains query arguments 'source' and 'pid' and 'source'=='templates'
// then we need to use the data from this template as data source to fill the form.
if (queryParams.source === 'templates' && queryParams.pid != null) {
record$ = this.recordService.getRecord('templates', queryParams.pid).pipe(map$4((record) => {
this.messageService.add({
severity: 'success',
summary: this.translateService.instant('Template'),
detail: this.translateService.instant('Template loaded'),
life: CONFIG.MESSAGE_LIFE
});
return {
result: true,
record: record.metadata.data
};
}));
// load data from existing document
// If the parsed url contains a 'pid' value, then user try to edit a record. Then
// we need to load this record and use it as data source to fill the form.
}
else if (this.pid) {
record$ = this.recordService
.getRecord(this.recordType, this.pid)
.pipe(switchMap((record) => {
return this.recordUiService.canUpdateRecord$(record, this.recordType).pipe(map$4((result) => {
return {
result,
record: record.metadata
};
}));
}));
}
combineLatest([schema$, record$]).subscribe({
next: ([schemaform, data]) => {
// Set schema
this.setSchema(this._resourceConfig.recordResource
? schemaform.schema.properties.metadata
: schemaform.schema);
// Check permissions and set record
if (data.result && data.result.can === false) {
this.messageService.add({
severity: 'error',
summary: this.translateService.instant(this.recordType),
detail: this.translateService.instant('You cannot update this record'),
sticky: true,
closable: true
});
this.location.back();
}
else {
this._setModel(data.record);
}
this.loadingChange.emit(false);
},
error: (error) => {
this.error = error;
this.loadingChange.emit(false);
}
});
});
}
/** onDestroy hook */
ngOnDestroy() {
this._subscribers.unsubscribe();
}
/**
* Set the model
* @param model: The data to use as new model
*/
_setModel(model) {
if (this._resourceConfig != null) {
// the parent don't know that we are editing a record
// /!\ This will probably not work anymore with resources managed by
// invenio-records-resources, a fix will be necessary to make it work
// with both systems.
if (!this._resourceConfig.recordResource &&
this.pid != null &&
(this.model == null || this.model.pid == null)) {
model.pid = this.pid;
}
// preprocess the model before sending to formly
this.model = this.preprocessRecord(model);
this.modelChanged(this.model);
}
}
/**
* Emit value when model is changed.
* @param modelValue Model.
*/
modelChanged(modelValue) {
this.modelChange.emit(removeEmptyValues(modelValue));
}
/**
* Preprocess the record before passing it to the editor
* @param record - Record object to preprocess
*/
preprocessRecord(record) {
const config = this.recordUiService.getResourceConfig(this.recordType);
if (config.preprocessRecordEditor) {
return config.preprocessRecordEditor(record);
}
return record;
}
/**
* Postprocess the record before save
* @param record - Record object to postprocess
*/
postprocessRecord(record) {
const config = this.recordUiService.getResourceConfig(this.recordType);
if (config.postprocessRecordEditor) {
return config.postprocessRecordEditor(record);
}
return record;
}
/**
* Pre Create Record
* @param record - Record object
*/
preCreateRecord(record) {
const config = this.recordUiService.getResourceConfig(this.recordType);
if (config.preCreateRecord) {
return config.preCreateRecord(record);
}
return record;
}
/**
* Pre Update Record
* @param record - Record object
*/
preUpdateRecord(record) {
const config = this.recordUiService.getResourceConfig(this.recordType);
if (config.preUpdateRecord) {
return config.preUpdateRecord(record);
}
return record;
}
/**
* Preprocess the record before passing it to the editor
* @param schema - object, JSONSchema
*/
setSchema(schema) {
// reorder all object properties
this.schema = processJsonSchema(resolve$ref(schema, schema.properties));
this.options = {};
// remove hidden field list in case of a previous setSchema call
this.clearHiddenFields();
// form configuration
const editorConfig = {
pid: this.pid,
longMode: this.longMode,
recordType: this.recordType
};
const fields = [
this.formlyJsonschema.toFieldConfig(this.schema, {
// post process JSONSChema7 to FormlyFieldConfig conversion
map: (field, jsonSchema) => {
/**** additional JSONSchema configurations *******/
field = this.jsonschemaService.processField(field, jsonSchema);
field.props.editorConfig = editorConfig;
field.props.getRoot = (() => this.rootField);
field.props.setHide = ((field, value) => this.setHide(field, value));
if (this._resourceConfig != null && this._resourceConfig.formFieldMap) {
return this._resourceConfig.formFieldMap(field, jsonSchema);
}
return field;
}
})
];
// mark the root field
fields[0].props.isRoot = true;
this.fields = fields;
// set root element
if (this.fields) {
this.title = this.fields[0].props?.label;
this.description = this.fields[0].props?.description;
this.rootField = this.fields[0];
}
}
/**
* Save the data on the server.
*/
submit() {
this.isSaveButtonDisabled = true;
this.form.markAllAsTouched();
if (this.form.valid === false) {
const fields = [];
Object.keys(this.form.controls).forEach((key) => {
if (this.form.controls[key].status !== 'VALID') {
fields.push(this.translateService.instant(key));
}
});
let errorMessage = '';
if (fields.length > 0) {
errorMessage += "\n" + this.translateService.instant('Field(s) in error: ');
errorMessage += fields.join(', ');
}
this.messageService.add({
severity: 'error',
summary: this.translateService.instant(this.recordType),
detail: this.translateService.instant('The form contains errors.') + errorMessage,
sticky: true,
closable: true
});
this.isSaveButtonDisabled = false;
return;
}
this._canDeactivate();
this.loadingChange.emit(true);
let data = removeEmptyValues(this.model);
data = this.postprocessRecord(data);
// For compatibility with resources managed with invenio-records-resources
if (this._resourceConfig.recordResource) {
data = { metadata: data };
}
// The record has validation, the action `save` is set.
if (data.metadata?.validation) {
data.metadata.validation.action = 'save';
}
let recordAction$;
if (this.pid != null) {
recordAction$ = this.recordService.update(this.recordType, this.pid, this.preUpdateRecord(data)).pipe(catchError((error) => this._handleError(error)), map$4(record => {
return { record, action: 'update', message: this.translateService.instant('Record updated.') };
}), finalize(() => this.isSaveButtonDisabled = false));
}
else {
recordAction$ = this.recordService.create(this.recordType, this.preCreateRecord(data)).pipe(catchError((error) => this._handleError(error)), map$4(record => {
return { record, action: 'create', message: this.translateService.instant('Record created.') };
}), finalize(() => this.isSaveButtonDisabled = false));
}
recordAction$.subscribe(result => {
this.messageService.add({
severity: 'success',
summary: this.translateService.instant(this.recordType),
detail: this.translateService.instant(result.message),
life: CONFIG.MESSAGE_LIFE
});
this.recordUiService.redirectAfterSave(result.record.id, result.record, this.recordType, result.action, this.route);
this.loadingChange.emit(true);
});
}
/** Save the current editor content as a template */
saveAsTemplate() {
this.ref = this.dialogService.open(SaveTemplateFormComponent, {
header: this.translateService.instant('Save as template'),
width: '40vw',
});
this.ref.onClose.subscribe(data => {
if (data) {
let modelData = removeEmptyValues(this.model);
modelData = this.postprocessRecord(modelData);
let record = {
name: data.name,
data: modelData,
template_type: this.recordType
};
const tmplConfig = this.recordUiService.getResourceConfig(this.editorSettings.template.recordType);
if (tmplConfig.preCreateRecord) {
record = tmplConfig.preCreateRecord(record);
}
this.loadingChange.emit(true);
this.recordService.create(this.editorSettings.template.recordType, record).subscribe({
next: (createdRecord) => {
this.messageService.add({
severity: 'success',
summary: this.translateService.instant(this.editorSettings.template.recordType),
detail: this.translateService.instant('Record created.'),
life: CONFIG.MESSAGE_LIFE
});
this.recordUiService.redirectAfterSave(createdRecord.id, createdRecord, this.editorSettings.template.recordType, 'create', this.route);
},
error: (error) => {
this.loadingChange.emit(false);
this.messageService.add({
severity: 'error',
summary: this.translateService.instant(this.editorSettings.template.recordType),
detail: error.title,
sticky: true,
closable: true
});
}
});
}
});
}
/**
* Scroll the window in to the DOM element corresponding to a given config field.
* @param event - click DOM event
* @param field - FormlyFieldConfig, the form config corresponding to the DOM element to jump to.
*/
setFocus(event, field) {
event.preventDefault();
this.setFieldFocus(field, true);
}
/**
* Cancel editing and back to previous page
*/
cancel() {
this._canDeactivate();
this.location.back();
}
/**
* Open a modal dialog box to load a template.
*/
showLoadTemplateDialog() {
this.dialogService.open(LoadTemplateFormComponent, {
header: this.translateService.instant('Load from template'),
focusOnShow: false,
width: '50vw',
position: 'top',
modal: true,
dismissableMask: false,
data: {
templateResourceType: this.editorSettings.template.recordType,
resourceType: this.recordType
}
});
}
/**
* Can the `load template` should be visible.
* The 'load template' button will be visible only if the corresponding setting is set and the user
* isn't in 'edit mode' (load template is only available for new resources)
* @return True if the button could be visible ; False otherwise
*/
canLoadTemplate() {
return this.editorSettings.template.loadFromTemplate && !this.pid;
}
/********** Form field manipulation **********/
/**
* Scroll to a field or fieldGroup and set focus in the first field candidate.
* @param field: the field or fieldGroup where to search about field candidate.
* @param scroll: is the screen should scroll to the field.
*/
setFieldFocus(field, scroll = false) {
if (scroll === true && field.id) {
const el = document.getElementById(`field-${field.id}`);
if (el != null) {
// we need to scroll after the focus setTimeout push the action in the
// next event loop.
setTimeout(() => el.scrollIntoView({ behavior: 'smooth' }));
scroll = false;
}
}
if (field.fieldGroup && field.fieldGroup.length > 0) {
const visibleFields = field.fieldGroup.filter(f => !f.hide);
if (visibleFields.length > 0) {
return this.setFieldFocus(visibleFields[0], scroll);
}
}
field.focus = true;
return true;
}
/**
* Add a field to the hidden list
* @param field - FormlyFieldConfig, form config to be added
*/
addHiddenField(field) {
if (this.isRoot(field.parent)) {
// formly can change the reference of a field. Especially when the model
// change such as external import.
this._hiddenFields = this._hiddenFields.filter(f => f.id !== field.id);
this._hiddenFields.push(field);
this._hiddenFieldsSubject.next(this._hiddenFields);
}
}
/**
* Clear the list of the hidden fields
*/
clearHiddenFields() {
this._hiddenFields = [];
this._hiddenFieldsSubject.next(this._hiddenFields);
}
/**
* Remove a field to the hidden list
* @param field - FormlyFieldConfig, form config to be removed
*/
removeHiddenField(field) {
if (this._hiddenFields.some(f => f.id === field.id) && this.isRoot(field.parent)) {
// Form is touched if a field is added.
// Required for hide field in edit mode.
this.rootField.formControl.markAsTouched();
this._hiddenFields = this._hiddenFields.filter(f => f.id !== field.id);
this._hiddenFieldsSubject.next(this._hiddenFields);
}
}
/**
* Am I at the root of the form?
* @param field - FormlyFieldConfig, the field to hide
* @returns boolean, true if I'm the root
*/
isRoot(field) {
if (field == null) {
return false;
}
return field.props?.isRoot || false;
}
/**
* Can the field be hidden?
* @param field - FormlyFieldConfig, the field to hide
* @returns boolean, true if the field can be hidden
*/
canHide(field) {
if (!this.longMode) {
return false;
}
return (!field.props.required &&
!field.hide &&
!('hide' in field?.expressions));
}
/**
* Hide the given formly field.
* @param field - FormlyFieldConfig, the field to hide
*/
setHide(field, value, scroll = false) {
if (value) {
if (field.parent.props.isRoot) {
this.addHiddenField(field);
}
}
else {
this.removeHiddenField(field);
this.setFieldFocus(field, scroll);
}
field.hide = value;
}
/********************* Private ***************************************/
/**
* Handle form error
* @param error: object with status and title parameters
* @return Observable<Error>
*/
_handleError(error) {
this.messageService.add({
severity: 'error',
summary: this.translateService.instant(error.title),
detail: this.translateService.instant('Server error.'),
sticky: true,
closable: true
});
return throwError(() => ({ status: error.status, title: error.title }));
}
_canDeactivate(activate = true) {
this.canDeactivate = activate;
this.canDeactivateChange.next(activate);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: EditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: EditorComponent, isStandalone: false, selector: "ng-core-editor", inputs: { model: "model", schema: "schema", editorSettings: "editorSettings", pid: "pid" }, outputs: { modelChange: "modelChange", loadingChange: "loadingChange", canDeactivateChange: "canDeactivateChange" }, usesInheritance: true, usesOnChanges: true, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"core:flex core:flex-col core:gap-1\">\n <div class=\"core:z-10 core:flex core:flex-col core:gap-2 core:bg-white core:sticky core:top-0 core:py-1\">\n <div class=\"core:flex core:justify-between core:items-center\">\n @if (rootField) {\n <legend>\n <span class=\"core:text-2xl\" [pTooltip]=\"description | translate\" tooltipPosition=\"top\">\n {{ title || recordType | ucfirst | translate }}\n </span>\n </legend>\n }\n <div class=\"core:flex core:items-center core:justify-end core:gap-1\">\n <!-- load from template button -->\n @if (canLoadTemplate()) {\n <p-button\n id=\"editor-load-template-button\"\n [label]=\"'Load template\u2026' | translate\"\n [outlined]=\"true\"\n icon=\"fa fa-upload\"\n (onClick)=\"showLoadTemplateDialog()\"\n />\n }\n <!-- cancel button -->\n <p-button\n id=\"editor-cancel-button\"\n [label]=\"'Cancel' | translate\"\n icon=\"fa fa-times\"\n [outlined]=\"true\"\n severity=\"danger\"\n (onClick)=\"cancel()\"\n />\n <!-- save button -->\n @if (saveAlternatives.length === 0) {\n <p-button\n id=\"editor-save-button\"\n [label]=\"'Save' | translate\"\n (onClick)=\"submit()\"\n [disabled]=\"isSaveButtonDisabled\"\n />\n } @else {\n <p-splitButton\n id=\"editor-save-button-split\"\n [label]=\"'Save' | translate\"\n (onClick)=\"submit()\"\n [model]=\"saveAlternatives\"\n />\n }\n </div>\n </div>\n <p-divider class=\"core:!mt-0\" />\n </div>\n\n <div class=\"core:flex core:gap-2 core:**:min-w-0\">\n <!-- add fields section -->\n @if (editorSettings.longMode) {\n <div class=\"core:flex core:top-18 core:sticky core:flex-[0_0_250px] core:h-[calc(100dvh-(--spacing(18)))] core:overflow-y-auto core:py-1\">\n <ng-core-editor-add-field-editor [editorComponent]=\"editorComponent\" />\n </div>\n }\n\n <!-- editor itself -->\n <div class=\"core:grow core:flex core:gap-2\">\n <!-- ngx-formly -->\n <formly-form\n class=\"core:grow\"\n [model]=\"model\"\n [fields]=\"fields\"\n [options]=\"options\"\n [form]=\"form\"\n (modelChange)=\"modelChanged($event)\"\n />\n @if (editorSettings.longMode) {\n <!-- TOC navigation -->\n <div class=\"core:text-sm core:hidden core:lg:block core:flex core:top-18 core:sticky core:flex-[0_0_250px] core:h-[calc(100dvh-(--spacing(18)))] core:overflow-y-auto core:flex-col core:gap-2\">\n <ng-content select=\"[beforeTableOfContent]\"></ng-content>\n <span class=\"core:font-bold\" for=\"addField\" translate>Jump to</span>\n <ul class=\"core:ml-2 core:pl-0 core:list-none\">\n @for (f of tocFields$ | async; track f.key) {\n <li class=\"core:mb-1\">\n <span class=\"text-link-secondary\" (click)=\"setFocus($event, f)\">{{ f.props.label | translate }}</span>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n </div>\n</form>\n\n@if (error) {\n <ng-core-error [error]=\"error\" />\n}\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["style", "styleClass", "layout", "type", "align"] }, { kind: "component", type: i3$3.SplitButton, selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "style", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "directive", type: i5.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: ErrorComponent, selector: "ng-core-error", inputs: ["error"] }, { kind: "directive", type: i3$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i4$1.LegacyFormlyForm, selector: "formly-form" }, { kind: "component", type: AddFieldEditorComponent, selector: "ng-core-editor-add-field-editor", inputs: ["editorComponent"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "pipe", type: UpperCaseFirstPipe, name: "ucfirst" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: EditorComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-editor', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<form [formGroup]=\"form\" (ngSubmit)=\"submit()\">\n <div class=\"core:flex core:flex-col core:gap-1\">\n <div class=\"core:z-10 core:flex core:flex-col core:gap-2 core:bg-white core:sticky core:top-0 core:py-1\">\n <div class=\"core:flex core:justify-between core:items-center\">\n @if (rootField) {\n <legend>\n <span class=\"core:text-2xl\" [pTooltip]=\"description | translate\" tooltipPosition=\"top\">\n {{ title || recordType | ucfirst | translate }}\n </span>\n </legend>\n }\n <div class=\"core:flex core:items-center core:justify-end core:gap-1\">\n <!-- load from template button -->\n @if (canLoadTemplate()) {\n <p-button\n id=\"editor-load-template-button\"\n [label]=\"'Load template\u2026' | translate\"\n [outlined]=\"true\"\n icon=\"fa fa-upload\"\n (onClick)=\"showLoadTemplateDialog()\"\n />\n }\n <!-- cancel button -->\n <p-button\n id=\"editor-cancel-button\"\n [label]=\"'Cancel' | translate\"\n icon=\"fa fa-times\"\n [outlined]=\"true\"\n severity=\"danger\"\n (onClick)=\"cancel()\"\n />\n <!-- save button -->\n @if (saveAlternatives.length === 0) {\n <p-button\n id=\"editor-save-button\"\n [label]=\"'Save' | translate\"\n (onClick)=\"submit()\"\n [disabled]=\"isSaveButtonDisabled\"\n />\n } @else {\n <p-splitButton\n id=\"editor-save-button-split\"\n [label]=\"'Save' | translate\"\n (onClick)=\"submit()\"\n [model]=\"saveAlternatives\"\n />\n }\n </div>\n </div>\n <p-divider class=\"core:!mt-0\" />\n </div>\n\n <div class=\"core:flex core:gap-2 core:**:min-w-0\">\n <!-- add fields section -->\n @if (editorSettings.longMode) {\n <div class=\"core:flex core:top-18 core:sticky core:flex-[0_0_250px] core:h-[calc(100dvh-(--spacing(18)))] core:overflow-y-auto core:py-1\">\n <ng-core-editor-add-field-editor [editorComponent]=\"editorComponent\" />\n </div>\n }\n\n <!-- editor itself -->\n <div class=\"core:grow core:flex core:gap-2\">\n <!-- ngx-formly -->\n <formly-form\n class=\"core:grow\"\n [model]=\"model\"\n [fields]=\"fields\"\n [options]=\"options\"\n [form]=\"form\"\n (modelChange)=\"modelChanged($event)\"\n />\n @if (editorSettings.longMode) {\n <!-- TOC navigation -->\n <div class=\"core:text-sm core:hidden core:lg:block core:flex core:top-18 core:sticky core:flex-[0_0_250px] core:h-[calc(100dvh-(--spacing(18)))] core:overflow-y-auto core:flex-col core:gap-2\">\n <ng-content select=\"[beforeTableOfContent]\"></ng-content>\n <span class=\"core:font-bold\" for=\"addField\" translate>Jump to</span>\n <ul class=\"core:ml-2 core:pl-0 core:list-none\">\n @for (f of tocFields$ | async; track f.key) {\n <li class=\"core:mb-1\">\n <span class=\"text-link-secondary\" (click)=\"setFocus($event, f)\">{{ f.props.label | translate }}</span>\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n </div>\n</form>\n\n@if (error) {\n <ng-core-error [error]=\"error\" />\n}\n" }]
}], ctorParameters: () => [], propDecorators: { model: [{
type: Input
}], modelChange: [{
type: Output
}], loadingChange: [{
type: Output
}], canDeactivateChange: [{
type: Output
}], schema: [{
type: Input
}], editorSettings: [{
type: Input
}], pid: [{
type: Input
}] } });
class Validators {
static datesGreaterThan(dateFirstName, dateLastName, targetField, strictMode = false) {
return datesGreaterThanValidator(dateFirstName, dateLastName, targetField, strictMode);
}
}
function datesGreaterThanValidator(dateFirstName, dateLastName, targetField, strict) {
return (control) => {
const dateFormat = 'yyyy-MM-dd';
const dateFirst = control.parent.get(dateFirstName);
const dateLast = control.parent.get(dateLastName);
if (dateFirst.value?.length > 0 && dateLast.value?.length > 0) {
const dateTimeFirst = DateTime.fromFormat(dateFirst.value, dateFormat);
const dateTimeLast = DateTime.fromFormat(dateLast.value, dateFormat);
const status = strict ? dateTimeFirst < dateTimeLast : dateTimeFirst <= dateTimeLast;
if (status) {
dateFirst.setErrors(null);
dateLast.setErrors(null);
}
return status;
}
return true;
};
}
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class NgCoreFormlyExtension {
constructor() {
this.recordService = inject(RecordService);
// Types to apply field wrapper on
this._fieldWrapperTypes = [
'boolean',
'datepicker',
'markdown',
'passwordGenerator',
'radioButton',
'remoteAutoComplete',
'textarea'
];
}
/**
* prePopulate Formly hook
* @param field - FormlyFieldConfig
*/
prePopulate(field) {
if (field.key) {
field.id = this._getKey(field);
}
this._setCustomValidators(field);
// Set default value from expression.
if (field.props?.defaultValueExpression) {
const expression = field.props.defaultValueExpression;
const expressionFn = Function('expression', `return ${expression};`);
field.defaultValue = expressionFn();
}
}
/**
* onPopulate Formly hook
* @param field - FormlyFieldConfig
*/
onPopulate(field) {
// Path of Array
// Bug issue: https://github.com/ngx-formly/ngx-formly/issues/3914
if (field?.parent?.type === 'array' && field.type !== 'object' && field.props.required) {
field.props.required = false;
}
this._setWrappers(field);
this._hideEmptyField(field);
if (field.props.alwaysHidden) {
field.props.itemCssClass = 'core:hidden';
}
const expressions = field?.expressions;
if (expressions && expressions['props.required'] !== undefined) {
field.options.fieldChanges.subscribe((changes) => {
if (changes.type === 'expressionChanges' &&
changes.property === 'props.required' &&
changes.value === true &&
changes.field.hide === true) {
changes.field.hide = false;
}
});
}
}
/**
* Add formly wrappers
* @param field - FormlyFieldConfig
*/
_setWrappers(field) {
// get wrappers from the props (JSONSchema)
if (field.props) {
field.wrappers = [...(field.props.wrappers || []), ...(field.wrappers || [])];
}
if (field?.props?.editorConfig?.longMode) {
// add automatically a card wrapper for the first level fields
const { parent } = field;
if (parent && parent.props && parent.props.isRoot === true && !field.wrappers.includes('card') && !field.wrappers.includes('hide')) {
field.wrappers.unshift('card');
}
}
// adds form-fields for non standard field types
if (this._fieldWrapperTypes.some((elem) => elem === field.type) && !field.wrappers.includes('card')) {
field.wrappers = [...(field.wrappers || []), 'form-field'];
}
// TODO: this can be fixed in a future formly release
// due to multiple calls (sometimes) we need to make the value unique
field.wrappers = [...new Set(field.wrappers)];
}
/**
* Create an id html attribute by joining field and parents keys
* @param field formly field config
* @returns array - keys to create the id html attribute
*/
_getKey(field) {
let parentKey = null;
const keyParams = [];
if (field.parent != null) {
parentKey = this._getKey(field.parent);
keyParams.push(parentKey);
}
if (parentKey != null) {
if (field.key != null) {
keyParams.push(field.key);
}
else {
keyParams.push(field.id.replace(/.*__(\d+)$/, '$1'));
}
return keyParams.join('-');
}
return field.key;
}
/**
* Check if the given field or one of his parents has a hide wrapper
* @param field - FormlyFieldConfig
* @return boolean - true if has a hide wrapper
*/
_hasHideWrapper(field) {
if (field.wrappers.includes('hide')) {
return true;
}
if (field?.parent) {
return this._hasHideWrapper(field.parent);
}
return false;
}
/**
* Check if the given field has at least on parent marked as hidden.
* The model should be empty.
*
* @param field - FormlyFieldConfig
* @return boolean - true has a parent marked as hidden.
*/
_hasHiddenParent(field) {
if (field?.hide === true) {
return this._modelIsEmpty(field);
}
if (field?.parent) {
return this._hasHiddenParent(field.parent);
}
return false;
}
/**
* Check if a field has an empty model.
*
* @param field formly field config
* @returns true if the given field has an empty model.
*/
_modelIsEmpty(field) {
let model = field?.model;
// for simple object the model is the parent dict
if (field.model != null && !field.hasOwnProperty('fieldGroup')) {
// New from ngx-formly v5.9.0
model = field.model[Array.isArray(field.key) ? field.key[0] : field.key];
}
model = removeEmptyValues(model);
return isEmpty(model);
}
/**
* Hide or show field depending of the data content and the hide property
* @param field - FormlyFieldConfig
*/
_hideEmptyField(field) {
if (!field.props?.editorConfig) {
return;
}
const { pid, longMode } = field.props?.editorConfig;
if (
// only in longMode else it will not be possible to unhide a field
!longMode ||
// system field has not key
!field?.key ||
// ignore array item which as key of the form "0"
// TODO: find a better way to identify this case
!isNaN(Number(field.key)) ||
// ignore field that has hide expression
'hide' in field?.expressions ||
// do not hide a field containing a 'hide' wrapper
this._hasHideWrapper(field) ||
// do not hide a field that has a parent marked as hidden and a model is empty
(this._hasHiddenParent(field?.parent) && field.props.hide !== true)) {
return;
}
// is the model empty
const modelEmpty = this._modelIsEmpty(field);
if (
// do not hide field which has value in the model
modelEmpty &&
// do not hide required fields
!field.props.initialRequired) {
if (
// hide field marked as hide
(field.props.hide === true &&
// do not hide field has been already manipulated
field.hide === undefined) ||
// in edition empty fields should be hidden
(pid != null &&
// only during the editor initialization
!field?.props?.getRoot()?.formControl?.touched)) {
field.props.setHide ? field.props.setHide(field, true) : (field.hide = true);
}
}
}
/**
* Set custom validators from the props configuration.
* @param field - FormlyFieldConfig
*/
_setCustomValidators(field) {
if (field.props == null || field.props.validation == null) {
return;
}
const customValidators = field.props.validation.validators || {};
// asyncValidators: valueAlreadyExists
if (customValidators.valueAlreadyExists) {
const { filter, limitToValues, term } = customValidators.valueAlreadyExists;
field.asyncValidators = {
validation: [
(control) => {
return this.recordService.uniqueValue(field, field.props.editorConfig.recordType, field.props.editorConfig.pid, term ? term : null, limitToValues ? limitToValues : [], filter ? filter : null);
},
],
};
delete customValidators.valueAlreadyExists;
}
// asyncValidators: valueKeysInObject
// This validator is similar to uniqueValidator but only check on some specific fields of array items.
if (customValidators.uniqueValueKeysInObject) {
field.validators = {
uniqueValueKeysInObject: {
expression: (control) => {
// if value isn't an array or array contains less than 2 elements, no need to check
if (!(control.value instanceof Array) || control.value.length < 2) {
return true;
}
const keysToKeep = customValidators.uniqueValueKeysInObject.keys;
const uniqueItems = Array.from(new Set(control.value.map((v) => {
const keys = keysToKeep.reduce((acc, elt) => {
acc[elt] = v[elt];
return acc;
}, {});
return JSON.stringify(keys);
})));
return uniqueItems.length === control.value.length;
},
},
};
}
// asyncValidators: numberOfSpecificValuesInObject
if (customValidators.numberOfSpecificValuesInObject) {
field.validators = {
numberOfSpecificValuesInObject: {
expression: (control) => {
function objIntersection(a, b) {
const k1 = Object.keys(a);
return k1.filter((k) => a[k] === b[k]);
}
const min = customValidators.numberOfSpecificValuesInObject.min || 0;
const max = customValidators.numberOfSpecificValuesInObject.max || Infinity;
const keys = customValidators.numberOfSpecificValuesInObject.keys;
// if value isn't an array and no minimum value is specified, no need to check.
if (!(control.value instanceof Array) && min === 0) {
return true;
}
const counter = control.value.filter((element) => objIntersection(keys, element).length > 0).length;
return min <= counter && counter <= max;
},
},
};
}
// The start date must be less than the end date.
if (customValidators.datesGreaterThan) {
const dateFirst = customValidators.datesGreaterThan.dateFirst;
const dateLast = customValidators.datesGreaterThan.dateLast;
const strict = customValidators.datesGreaterThan.strict || false;
const updateOn = customValidators.datesGreaterThan.updateOn || 'blur';
const fieldKey = String(field.key);
field.validators = {
datesGreaterThan: {
updateOn,
expression: (control) => Validators.datesGreaterThan(dateFirst, dateLast, fieldKey, strict)(control),
message: () => field.props.validation.messages.datesGreaterThan
},
};
}
}
}
class TranslateExtension {
constructor() {
this.translate = inject(TranslateService);
}
prePopulate(field) {
const props = field.props || {};
// translate only once
if (props.translated) {
return;
}
props.translated = true;
// label / title
if (props.label) {
// store the untranslated label as the hidden fields are not translated
props.untranslatedLabel = props.label;
field.expressions = {
...(field.expressions || {}),
'props.label': this.translate.stream(props.label),
};
}
// description
if (props.description) {
field.expressions = {
...(field.expressions || {}),
'props.description': this.translate.stream(props.description),
};
}
// placeholder
if (props.placeholder) {
field.expressions = {
...(field.expressions || {}),
'props.placeholder': this.translate.stream(props.placeholder),
};
}
// Save untranslated string
if (props.addonLeft) {
props.addonLeftUntranslated = props.addonLeft;
}
if (props.addonRight) {
props.addonRightUntranslated = props.addonRight;
}
this.processAllAddon(props);
this.translate.onLangChange.subscribe(() => {
this.processAllAddon(props);
});
}
processAllAddon(props) {
if (props.addonLeft) {
props.addonLeft = this.processAddon(props.addonLeftUntranslated);
}
if (props.addonRight) {
props.addonRight = this.processAddon(props.addonRightUntranslated);
}
}
processAddon(addon) {
return addon.map((label) => (label.startsWith('<') ? label : this.translate.instant(label)));
}
}
class FormOptionsProcessExtension {
prePopulate(field) {
// Process options
if (field.props?.options && !isObservable(field.props.options)) {
field.props.options = of(this.processOptions(field.props));
}
// Process filter options
if (field.props?.filters?.options && !isObservable(field.props.filters.options)) {
field.props.filters.options = of(this.processOptions(field.props.filters));
}
}
processOptions(props) {
this.processUntranslatedLabel(props.options);
const preferredOptions = props.options.filter((option) => option.preferred);
if (preferredOptions.length > 0) {
const options = [];
props.group = true;
const otherOptions = props.options.filter((option) => !option.preferred);
options.push({
label: 'group-preferred',
untranslatedLabel: 'group-preferred',
items: props.sort ? this.sortOptions(preferredOptions) : preferredOptions
});
options.push({
label: 'group-other',
untranslatedLabel: 'group-other',
items: props.sort ? this.sortOptions(otherOptions) : otherOptions
});
return options;
}
else {
return props.sort ? this.sortOptions(props.options) : props.options;
}
}
processUntranslatedLabel(options) {
options.map((option) => {
option.untranslatedLabel = option.label;
if (option.items) {
this.processUntranslatedLabel(option.items);
}
if (option.children) {
this.processUntranslatedLabel(option.children);
}
});
}
sortOptions(options) {
options = options.sort((a, b) => a.label.localeCompare(b.label));
if (options.filter((option) => option.items).length > 0) {
options.forEach((option) => {
if (option.items) {
return this.sortOptions(option.items);
}
});
}
return options;
}
}
/**
* To register an ngx-formly translations extension.
*
* @param translate ngx-translate service
* @param recordService - ng core record service
* @returns FormlyConfig object configuration
*/
function registerNgCoreFormlyExtension(translate, recordService) {
return {
// translate the default validators messages
// widely inspired from ngx-formly example
validationMessages: [
{
name: 'required',
// use a marker to force translation extraction due to a bad detection of ngx-translate-extract
message: () => translate.stream(_('This field is required')),
},
{
name: 'null',
message: () => translate.stream(_('should be null')),
},
{
name: 'uniqueItems',
message: () => translate.stream(_('should NOT have duplicate items')),
},
{
name: 'uniqueValueKeysInObject',
message: () => translate.stream(_('should NOT have duplicate items')),
},
{
name: 'numberOfSpecificValuesInObject',
message: (_err, field) => {
const validatorConfig = field.props.customValidators.numberOfSpecificValuesInObject;
const min = validatorConfig.min || 0;
const max = validatorConfig.max || Infinity;
const { keys } = validatorConfig;
// Build a string based on validators specified keys
// the object `{a: 'testA', b= 'testB'}` will be transform to a string `a=testA and b=testB`
const keysString = Object.keys(keys).map((key) => key + '=' + translate.instant(keys[key]));
const joinKeysString = keysString.join(' ' + translate.instant('and') + ' ');
// Build a string based on min/max values specified into configuration
// depending of min/max configuration, the message must be different
let counterMessage = '';
if (min > 0 && max < Infinity) {
counterMessage =
min === max
? translate.instant(_('strictly {{counter}}'), { counter: min })
: translate.instant(_('between {{min}} and {{max}}'), { min, max });
}
else if (min > 0) {
counterMessage = translate.instant(_('minimum {{min}}'), { min });
}
else if (max < Infinity) {
counterMessage = translate.instant(_('maximum {{max}}'), { max });
}
// Combine string to return a full sentence
return translate.instant(_('should have {{counter}} {{keys}}'), {
counter: counterMessage,
keys: joinKeysString,
});
},
},
{
name: 'alreadyTaken',
message: () => translate.stream(_('the value is already taken')),
},
{
name: 'minLength',
message: (_err, field) => translate.stream(_('should NOT be shorter than {{minLength}} characters'), {
minLength: field.props.minLength,
}),
},
{
name: 'maxLength',
message: (_err, field) => translate.stream(_('should NOT be longer than {{maxLength}} characters'), {
maxLength: field.props.maxLength,
}),
},
{
name: 'minItems',
message: (_err, field) => translate.stream(_('should NOT have fewer than {{minItems}} items'), {
minItems: field.props.minItems,
}),
},
{
name: 'maxItems',
message: (_err, field) => translate.stream(_('should NOT have more than {{maxItems}} items'), {
maxItems: field.props.maxItems,
}),
},
{
name: 'min',
message: (_err, field) => translate.stream(_('should be >= {{min}}'), { min: field.props.min }),
},
{
name: 'max',
message: (_err, field) => translate.stream(_('should be <= {{max}}'), { max: field.props.max }),
},
{
name: 'exclusiveMinimum',
message: (_err, field) => translate.stream(_('should be > {{step}}'), { step: field.props.step }),
},
{
name: 'exclusiveMaximum',
message: (_err, field) => translate.stream(_('should be < {{step}}'), { step: field.props.step }),
},
{
name: 'multipleOf',
message: (_err, field) => translate.stream(_('should be multiple of ${{step}}'), { step: field.props.step }),
},
{
name: 'const',
message: (_err, field) => translate.stream(_('should be equal to constant "{{const}}"'), { const: field.props.const }),
},
],
extensions: [
{
name: 'form-options',
extension: new FormOptionsProcessExtension(),
priority: 12
},
{
name: 'translate',
extension: new TranslateExtension(),
// Execute Translate extension after ng-core extension
priority: 11,
},
{
name: 'ng-core',
extension: new NgCoreFormlyExtension(),
// Execute Core Formly extension after formly processing (priority low)
// https://main.formly.dev/docs/guide/custom-formly-extension#extension-priority
priority: 10,
},
],
};
}
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class DatePickerComponent extends FieldType {
constructor() {
super(...arguments);
this.locale = inject(LOCALE_ID);
this.value = model();
this.subscription = new Subscription();
this.defaultOptions = {
props: {
appendTo: 'body',
clearButtonStyleClass: 'p-button-text',
dateFormat: 'yy-mm-dd',
disabled: false,
firstDayOfWeek: 0,
fluid: true,
inline: false,
numberOfMonths: 1,
panelStyleClass: 'core:!min-w-0',
placeholder: 'Select…',
required: false,
showButtonBar: false,
showIcon: true,
showOnFocus: true,
stepHour: 1,
stepMinute: 1,
stepSecond: 1,
todayButtonStyleClass: 'p-button-text',
view: 'date'
},
};
this.defaultDate = undefined;
this.disabledDates = undefined;
this.maxDate = undefined;
this.minDate = undefined;
}
ngOnInit() {
this.subscription.add(this.formControl.valueChanges.subscribe((value) => {
const date = new Date(value);
if (this.value() !== date) {
this.value.set(date);
}
}));
let { value } = this.formControl;
if (value) {
this.value.set(new Date(value));
}
if (!this.formControl.value && this.props.defaultDate) {
this.defaultDate = this.processDate(this.props.defaultDate);
}
if (this.props.disabledDates) {
this.disabledDates = this.props.disabledDates.map((date) => this.processDate(date));
}
if (this.props.minDate) {
this.minDate = this.processDate(this.props.minDate);
}
if (this.props.maxDate) {
this.maxDate = this.processDate(this.props.maxDate);
}
this.subscription.add(this.value.subscribe((value) => {
if (value) {
const convertedDate = this.outputDate(value);
if (convertedDate !== this.formControl.value) {
this.formControl.patchValue(convertedDate);
}
}
}));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
processDate(date) {
return date instanceof Date ? date : new Date(date);
}
outputDate(value) {
return DateTime
.fromJSDate(this.processDate(value))
.toFormat('yyyy-MM-dd');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DatePickerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: DatePickerComponent, isStandalone: false, selector: "ng-core-date-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, usesInheritance: true, ngImport: i0, template: `
<p-datepicker
[(ngModel)]="value"
[formlyAttributes]="field"
[appendTo]="props.appendTo"
[clearButtonStyleClass]="props.clearButtonStyleClass"
[dateFormat]="props.dateFormat"
[defaultDate]="props.defaultDate"
[disabledDates]="disabledDates"
[disabledDays]="props.disabledDays"
[firstDayOfWeek]="props.firstDayOfWeek"
[fluid]="props.fluid"
[hourFormat]="props.hourFormat"
[inline]="props.inline"
[inputStyleClass]="props.inputStyleClass"
[maxDate]="maxDate"
[minDate]="minDate"
[numberOfMonths]="props.numberOfMonths"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder"
[readonlyInput]="props.readonlyInput"
[required]="props.required"
selectionMode="single"
[showButtonBar]="props.showButtonBar"
[showIcon]="props.showIcon"
[showOnFocus]="props.showOnFocus"
[stepHour]="props.stepHour"
[stepMinute]="props.stepMinute"
[stepSecond]="props.stepSecond"
[styleClass]="props.styleClass"
[todayButtonStyleClass]="props.todayButtonStyleClass"
[view]="props.view"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: i2$4.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "fluid", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "size", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DatePickerComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-date-picker',
template: `
<p-datepicker
[(ngModel)]="value"
[formlyAttributes]="field"
[appendTo]="props.appendTo"
[clearButtonStyleClass]="props.clearButtonStyleClass"
[dateFormat]="props.dateFormat"
[defaultDate]="props.defaultDate"
[disabledDates]="disabledDates"
[disabledDays]="props.disabledDays"
[firstDayOfWeek]="props.firstDayOfWeek"
[fluid]="props.fluid"
[hourFormat]="props.hourFormat"
[inline]="props.inline"
[inputStyleClass]="props.inputStyleClass"
[maxDate]="maxDate"
[minDate]="minDate"
[numberOfMonths]="props.numberOfMonths"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder"
[readonlyInput]="props.readonlyInput"
[required]="props.required"
selectionMode="single"
[showButtonBar]="props.showButtonBar"
[showIcon]="props.showIcon"
[showOnFocus]="props.showOnFocus"
[stepHour]="props.stepHour"
[stepMinute]="props.stepMinute"
[stepSecond]="props.stepSecond"
[styleClass]="props.styleClass"
[todayButtonStyleClass]="props.todayButtonStyleClass"
[view]="props.view"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
}]
}] });
class NgCoreFormlyDatePickerModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyDatePickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyDatePickerModule, declarations: [DatePickerComponent], imports: [CommonModule,
DatePickerModule,
FormsModule,
ReactiveFormsModule, i4$1.FormlyModule], exports: [DatePickerComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyDatePickerModule, imports: [CommonModule,
DatePickerModule,
FormsModule,
ReactiveFormsModule,
FormlyModule.forChild({
types: [
{
name: 'datePicker',
component: DatePickerComponent,
wrappers: ['form-field'],
},
{
name: 'dateTimePicker', // For compatibility
extends: 'datePicker'
}
],
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyDatePickerModule, decorators: [{
type: NgModule,
args: [{
declarations: [DatePickerComponent],
imports: [
CommonModule,
DatePickerModule,
FormsModule,
ReactiveFormsModule,
FormlyModule.forChild({
types: [
{
name: 'datePicker',
component: DatePickerComponent,
wrappers: ['form-field'],
},
{
name: 'dateTimePicker', // For compatibility
extends: 'datePicker'
}
],
}),
],
exports: [DatePickerComponent]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class NgCoreFormlyFieldInput extends FieldType {
constructor() {
super(...arguments);
this.defaultOptions = {
props: {
class: 'core:w-full',
inputStep: 'any'
}
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyFieldInput, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: NgCoreFormlyFieldInput, isStandalone: false, selector: "ng-core-formly-field-primeng-input", usesInheritance: true, ngImport: i0, template: `
@if (props.addonLeft || props.addonRight) {
<p-inputgroup>
@if (props.addonLeft) {
@for (prop of props.addonLeft; track prop) {
<p-inputgroup-addon [innerHTML]="prop"></p-inputgroup-addon>
}
}
<ng-container
[ngTemplateOutlet]="input"
[ngTemplateOutletContext]="{ formControl, props, field, showError }"
/>
@if (props.addonRight) {
@for (prop of props.addonRight; track prop) {
<p-inputgroup-addon [innerHTML]="prop"></p-inputgroup-addon>
}
}
</p-inputgroup>
} @else {
<ng-container
[ngTemplateOutlet]="input"
[ngTemplateOutletContext]="{ formControl, props, field, showError }"
/>
}
<ng-template #input let-formControl="formControl" let-props="props" let-field="field" let-showError="showError">
@if (props.type === 'number') {
<input
pInputText
[class]="props.class"
type="number"
[attr.step]="props.inputStep"
[formControl]="formControl"
[formlyAttributes]="field"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
} @else {
<input
pInputText
[class]="props.class"
[type]="props.type || 'text'"
[formControl]="formControl"
[formlyAttributes]="field"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
}
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3$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: i3$2.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i3$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "component", type: i4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["style", "styleClass"] }, { kind: "component", type: i3.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyFieldInput, decorators: [{
type: Component,
args: [{
selector: 'ng-core-formly-field-primeng-input',
template: `
@if (props.addonLeft || props.addonRight) {
<p-inputgroup>
@if (props.addonLeft) {
@for (prop of props.addonLeft; track prop) {
<p-inputgroup-addon [innerHTML]="prop"></p-inputgroup-addon>
}
}
<ng-container
[ngTemplateOutlet]="input"
[ngTemplateOutletContext]="{ formControl, props, field, showError }"
/>
@if (props.addonRight) {
@for (prop of props.addonRight; track prop) {
<p-inputgroup-addon [innerHTML]="prop"></p-inputgroup-addon>
}
}
</p-inputgroup>
} @else {
<ng-container
[ngTemplateOutlet]="input"
[ngTemplateOutletContext]="{ formControl, props, field, showError }"
/>
}
<ng-template #input let-formControl="formControl" let-props="props" let-field="field" let-showError="showError">
@if (props.type === 'number') {
<input
pInputText
[class]="props.class"
type="number"
[attr.step]="props.inputStep"
[formControl]="formControl"
[formlyAttributes]="field"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
} @else {
<input
pInputText
[class]="props.class"
[type]="props.type || 'text'"
[formControl]="formControl"
[formlyAttributes]="field"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
}
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class NgCoreFormlyInputModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyInputModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyInputModule, declarations: [NgCoreFormlyFieldInput], imports: [CommonModule,
ReactiveFormsModule,
InputTextModule,
InputNumberModule,
InputGroupModule,
InputGroupAddonModule,
FormlyFormFieldModule, i4$1.FormlyModule], exports: [NgCoreFormlyFieldInput] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyInputModule, imports: [CommonModule,
ReactiveFormsModule,
InputTextModule,
InputNumberModule,
InputGroupModule,
InputGroupAddonModule,
FormlyFormFieldModule,
FormlyModule.forChild({
types: [
{
name: 'input',
component: NgCoreFormlyFieldInput,
wrappers: ['form-field'],
},
{ name: 'string', extends: 'input' },
{
name: 'number',
extends: 'input',
defaultOptions: {
props: {
type: 'number',
},
},
},
{
name: 'integer',
extends: 'input',
defaultOptions: {
props: {
type: 'number',
},
},
},
],
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyInputModule, decorators: [{
type: NgModule,
args: [{
declarations: [NgCoreFormlyFieldInput],
imports: [
CommonModule,
ReactiveFormsModule,
InputTextModule,
InputNumberModule,
InputGroupModule,
InputGroupAddonModule,
FormlyFormFieldModule,
FormlyModule.forChild({
types: [
{
name: 'input',
component: NgCoreFormlyFieldInput,
wrappers: ['form-field'],
},
{ name: 'string', extends: 'input' },
{
name: 'number',
extends: 'input',
defaultOptions: {
props: {
type: 'number',
},
},
},
{
name: 'integer',
extends: 'input',
defaultOptions: {
props: {
type: 'number',
},
},
},
],
}),
],
exports: [NgCoreFormlyFieldInput]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TranslateLabelService {
constructor() {
this.translateService = inject(TranslateService);
}
translateLabel(options) {
options.map((option) => {
if (!option.untranslatedLabel) {
option.untranslatedLabel = option.label;
}
option.label = this.translateService.instant(option.untranslatedLabel);
if (option.items) {
this.translateLabel(option.items);
}
if (option.children) {
this.translateLabel(option.children);
}
});
return options;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLabelService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLabelService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TranslateLabelService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class MultiCheckboxComponent extends FieldType {
constructor() {
super(...arguments);
this.translateService = inject(TranslateService);
this.translateLabelService = inject(TranslateLabelService);
this.ref = inject(ChangeDetectorRef);
this.subscription = new Subscription();
/** Default options */
this.defaultOptions = {
props: {
style: 'stacked'
}
};
this.multiCheckBoxValue = [];
}
ngOnInit() {
const optionsObs = this.props.options;
const changeObs = this.translateService.onLangChange.pipe(switchMap$1(() => this.optionValues$));
this.optionValues$ = merge(...[optionsObs, changeObs])
.pipe(map$5(options => {
this.ref.markForCheck();
return this.translateLabelService.translateLabel(options);
}));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
onChange(event) {
this.field.formControl.patchValue(event.checked);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MultiCheckboxComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: MultiCheckboxComponent, isStandalone: false, selector: "ng-core-multi-checkbox", usesInheritance: true, ngImport: i0, template: `
<div class="core:flex" [ngClass]="{ 'core:gap-3': props.style === 'inline', 'core:flex-col core:gap-1': props.style === 'stacked', 'p-inputtext ng-invalid ng-dirty': showError }">
@for (option of optionValues$|async|keyvalue; track option.key) {
@let fieldKey = field.key + '_' + option.key;
<div class="core:flex core:items-center core:gap-2">
<p-checkbox
[disabled]="option.value.disabled"
[formlyAttributes]="field"
[inputId]="fieldKey"
[name]="field.key"
[ngModel]="field.formControl.value"
[readonly]="props.readonly"
[styleClass]="props.styleClass"
[value]="option.value.value"
(onChange)="onChange($event)"
/>
<label [for]="fieldKey" [class]="props.labelStyleClass">{{ option.value.label }}</label>
</div>
}
</div>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: i2$5.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.KeyValuePipe, name: "keyvalue" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MultiCheckboxComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-multi-checkbox',
template: `
<div class="core:flex" [ngClass]="{ 'core:gap-3': props.style === 'inline', 'core:flex-col core:gap-1': props.style === 'stacked', 'p-inputtext ng-invalid ng-dirty': showError }">
@for (option of optionValues$|async|keyvalue; track option.key) {
@let fieldKey = field.key + '_' + option.key;
<div class="core:flex core:items-center core:gap-2">
<p-checkbox
[disabled]="option.value.disabled"
[formlyAttributes]="field"
[inputId]="fieldKey"
[name]="field.key"
[ngModel]="field.formControl.value"
[readonly]="props.readonly"
[styleClass]="props.styleClass"
[value]="option.value.value"
(onChange)="onChange($event)"
/>
<label [for]="fieldKey" [class]="props.labelStyleClass">{{ option.value.label }}</label>
</div>
}
</div>
`,
standalone: false
}]
}] });
class NgCoreFormlyMultiCheckboxModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiCheckboxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiCheckboxModule, declarations: [MultiCheckboxComponent], imports: [CommonModule,
CheckboxModule,
FormsModule, i5.TranslateModule, FormlySelectModule, i4$1.FormlyModule], exports: [MultiCheckboxComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiCheckboxModule, imports: [CommonModule,
CheckboxModule,
FormsModule,
TranslateModule.forChild(),
FormlySelectModule,
FormlyModule.forChild({
types: [
{
name: 'multi-checkbox',
component: MultiCheckboxComponent,
wrappers: ['form-field'],
}
],
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiCheckboxModule, decorators: [{
type: NgModule,
args: [{
declarations: [MultiCheckboxComponent],
imports: [
CommonModule,
CheckboxModule,
FormsModule,
TranslateModule.forChild(),
FormlySelectModule,
FormlyModule.forChild({
types: [
{
name: 'multi-checkbox',
component: MultiCheckboxComponent,
wrappers: ['form-field'],
}
],
}),
],
exports: [MultiCheckboxComponent]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class MultiSelectComponent extends FieldType {
constructor() {
super(...arguments);
this.translateService = inject(TranslateService);
this.translateLabelService = inject(TranslateLabelService);
this.ref = inject(ChangeDetectorRef);
this.subscription = new Subscription();
this.filter = false;
/** Default properties */
this.defaultOptions = {
props: {
display: 'comma',
editable: false,
filterMatchMode: 'contains',
fluid: true,
group: false,
placeholder: 'Select…',
required: false,
scrollHeight: CONFIG.DEFAULT_SELECT_SCROLL_HEIGHT,
sort: false,
tooltipPosition: 'top',
tooltipPositionStyle: 'absolute',
variant: 'outlined',
},
};
}
ngOnInit() {
const optionsObs = this.props.options;
const changeObs = this.translateService.onLangChange.pipe(switchMap$1(() => this.optionValues$));
this.optionValues$ = merge(...[optionsObs, changeObs])
.pipe(tap$1((options) => {
this.filter = this.enableFilter(options, 0).enabled;
}), map$5(options => {
this.ref.markForCheck();
return this.translateLabelService.translateLabel(options);
}));
}
/**
* Check if the filter feature of the select should be enabled.
*
* This function is interrupted if the threshold is reached.
*
* @param options the list of options
* @param numberOfOptions the numberOfOptions used by the recursion
* @returns enabled: true if the filter should be enabled, the current total of detected options (used by the recursion)
*/
enableFilter(options, numberOfOptions = 0) {
if (!options?.length) {
return { total: numberOfOptions, enabled: false };
}
// number of entries to enable the filter
const limit = 5;
let total = numberOfOptions + options.length;
if (total > limit) {
return { enabled: true, total };
}
for (const option of options) {
const child = this.enableFilter(option?.items, total);
total = child.total;
if (total > limit) {
return { enabled: total > limit, total };
}
}
return { enabled: total > limit, total };
}
// Clear all validators except required.
clearValidators() {
const { errors } = this.formControl;
this.formControl.setErrors(errors.required ? { required: true } : null);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MultiSelectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: MultiSelectComponent, isStandalone: false, selector: "ng-core-multi-select", usesInheritance: true, ngImport: i0, template: `
<p-multiSelect
[appendTo]="props.appendTo"
[class]="props.class"
[display]="props.display"
[dropdownIcon]="props.dropdownIcon"
[emptyFilterMessage]="props.emptyFilterMessage"
[emptyMessage]="props.emptyMessage"
[filter]="filter"
filterBy="label"
[fluid]="props.fluid"
[formControl]="formControl"
[formlyAttributes]="field"
[group]="props.group"
[loadingIcon]="props.loadingIcon"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
[options]="optionValues$|async"
optionLabel="label"
optionValue="value"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder | translate"
[required]="props.required"
[showClear]="!props.required"
[styleClass]="props.styleClass"
[tooltip]="props.tooltip"
[tooltipPosition]="props.tooltipPosition"
[tooltipPositionStyle]="props.tooltipPositionStyle"
[tooltipStyleClass]="props.tooltipStyleClass"
(onChange)="props.change && props.change(field, $event)"
(onClear)="clearValidators()"
>
<ng-template #selectedItems let-items>
@for(option of items; track option.value; let last = $last) {
<div class="core:inline-flex core:items-center core:gap-2 core:px-1">
<div>
{{ option.label }}@if(!last) {, }
</div>
</div>
}
@if (!items || items.length === 0) {
<div>{{ props.placeholder | translate }}</div>
}
</ng-template>
<ng-template let-item #item>
<span class="core:whitespace-normal">
{{item.label}}
</span>
</ng-template>
<ng-template let-group #group>
@if (group.untranslatedLabel !== 'group-preferred' && group.untranslatedLabel !== 'group-other') {
<div class="core:py-2 core:font-bold">{{ group.label }}</div>
} @else if (group.label === 'group-other') {
<div class="core:py-2"><hr></div>
}
</ng-template>
</p-multiSelect>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: i3$4.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "style", "styleClass", "panelStyle", "panelStyleClass", "inputId", "disabled", "fluid", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "variant", "appendTo", "dataKey", "name", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "size", "showClear", "autofocus", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "defaultLabel", "placeholder", "options", "filterValue", "itemSize", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MultiSelectComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-multi-select',
template: `
<p-multiSelect
[appendTo]="props.appendTo"
[class]="props.class"
[display]="props.display"
[dropdownIcon]="props.dropdownIcon"
[emptyFilterMessage]="props.emptyFilterMessage"
[emptyMessage]="props.emptyMessage"
[filter]="filter"
filterBy="label"
[fluid]="props.fluid"
[formControl]="formControl"
[formlyAttributes]="field"
[group]="props.group"
[loadingIcon]="props.loadingIcon"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
[options]="optionValues$|async"
optionLabel="label"
optionValue="value"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder | translate"
[required]="props.required"
[showClear]="!props.required"
[styleClass]="props.styleClass"
[tooltip]="props.tooltip"
[tooltipPosition]="props.tooltipPosition"
[tooltipPositionStyle]="props.tooltipPositionStyle"
[tooltipStyleClass]="props.tooltipStyleClass"
(onChange)="props.change && props.change(field, $event)"
(onClear)="clearValidators()"
>
<ng-template #selectedItems let-items>
@for(option of items; track option.value; let last = $last) {
<div class="core:inline-flex core:items-center core:gap-2 core:px-1">
<div>
{{ option.label }}@if(!last) {, }
</div>
</div>
}
@if (!items || items.length === 0) {
<div>{{ props.placeholder | translate }}</div>
}
</ng-template>
<ng-template let-item #item>
<span class="core:whitespace-normal">
{{item.label}}
</span>
</ng-template>
<ng-template let-group #group>
@if (group.untranslatedLabel !== 'group-preferred' && group.untranslatedLabel !== 'group-other') {
<div class="core:py-2 core:font-bold">{{ group.label }}</div>
} @else if (group.label === 'group-other') {
<div class="core:py-2"><hr></div>
}
</ng-template>
</p-multiSelect>
`,
standalone: false
}]
}] });
class NgCoreFormlyMultiSelectModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiSelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiSelectModule, declarations: [MultiSelectComponent], imports: [CommonModule,
ReactiveFormsModule, i5.TranslateModule, FormlySelectModule,
MultiSelectModule, i4$1.FormlyModule], exports: [MultiSelectComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiSelectModule, imports: [CommonModule,
ReactiveFormsModule,
TranslateModule.forChild(),
FormlySelectModule,
MultiSelectModule,
FormlyModule.forChild({
types: [
{
name: 'multi-select',
component: MultiSelectComponent,
wrappers: ['form-field'],
},
],
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyMultiSelectModule, decorators: [{
type: NgModule,
args: [{
declarations: [MultiSelectComponent],
imports: [
CommonModule,
ReactiveFormsModule,
TranslateModule.forChild(),
FormlySelectModule,
MultiSelectModule,
FormlyModule.forChild({
types: [
{
name: 'multi-select',
component: MultiSelectComponent,
wrappers: ['form-field'],
},
],
}),
],
exports: [MultiSelectComponent],
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Override this service in your application to implement your logic
class RemoteAutocompleteService {
getSuggestions(query, queryOptions, currentPid) {
return of([{ label: "test" }]);
}
getValueAsHTML(queryOptions, item) {
return of(item.label);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RemoteAutocompleteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RemoteAutocompleteService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RemoteAutocompleteService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RemoteAutocomplete extends FieldType {
constructor() {
super(...arguments);
this.translateService = inject(TranslateService);
this.remoteAutocompleteService = inject(RemoteAutocompleteService);
this.route = inject(ActivatedRoute);
this.ref = inject(ChangeDetectorRef);
this.query = new Subject();
this.onValueSelect = new Subject();
this.suggestions = toSignal(this.query.pipe(switchMap$1((data) => this.remoteAutocompleteService.getSuggestions(data.query, data.queryOptions, data.recordPid))), { initialValue: [] });
this.valueSelected = toSignal(this.onValueSelect.pipe(switchMap$1((data) => this.remoteAutocompleteService.getValueAsHTML(data.queryOptions, data.item))));
/** Default properties */
this.defaultOptions = {
props: {
delay: 300,
group: false,
minLength: 3,
scrollHeight: CONFIG.DEFAULT_SELECT_SCROLL_HEIGHT,
queryOptions: {
allowAdd: false,
maxOfResult: 100
}
}
};
this.value = '';
}
ngOnInit() {
if (this.props.filters) {
const optionsObs = this.props.filters.options;
const changeObs = this.translateService.onLangChange.pipe(switchMap$1(() => this.optionValues$));
this.optionValues$ = merge(...[optionsObs, changeObs])
.pipe(map$5(options => {
this.ref.markForCheck();
return this.translateLabel(options);
}));
}
}
ngAfterViewInit() {
if (this.field.props.filters) {
this.field.props.queryOptions.filter = this.field.props.filters.selected;
}
if (this.field.formControl?.value?.length > 0) {
this.onValueSelect.next({
item: { label: this.field.formControl.value, value: this.field.formControl.value },
queryOptions: { ...this.field.props.queryOptions },
});
}
}
changeFilter(filter) {
this.field.props.queryOptions.filter = filter.value;
this.value = '';
}
search(event) {
this.query.next({
query: event.query,
queryOptions: { ...this.field.props.queryOptions },
recordPid: this.route.snapshot.params.pid || null
});
}
onSelect(event) {
this.onValueSelect.next({
item: event.value,
queryOptions: { ...this.field.props.queryOptions },
});
this.formControl.patchValue(event.value.value);
}
clear() {
this.value = '';
this.formControl.reset(null);
this.field.focus = true;
}
translateLabel(options) {
options.map((option) => {
if (!option.untranslatedLabel) {
option.untranslatedLabel = option.label;
}
option.label = this.translateService.instant(option.untranslatedLabel);
});
return options;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RemoteAutocomplete, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: RemoteAutocomplete, isStandalone: false, selector: "ng-core-remote-autocomplete", usesInheritance: true, ngImport: i0, template: `
<div style="padding-inline: 0; padding-block: 0" class="core:flex core:gap-1" [ngClass]="{ 'p-inputtext ng-invalid ng-dirty': showError }">
@if (!field.formControl.value) {
@if (props.filters?.options) {
<p-select
[options]="optionValues$|async"
[ngModel]="props.filters.selected"
(onChange)="changeFilter($event)"
>
<ng-template let-selected #selectedItem>
{{ selected.untranslatedLabel | translate }}
</ng-template>
</p-select>
}
<p-autoComplete
class="core:grow"
styleClass="core:w-full"
inputStyleClass="core:w-full"
[scrollHeight]="props.scrollHeight"
[minLength]="props.minLength"
[maxlength]="props.maxLength"
[(ngModel)]="value"
[placeholder]="props.placeholder"
[group]="props.group"
[suggestions]="suggestions()"
(completeMethod)="search($event)"
(onSelect)="onSelect($event)"
>
<ng-template #item let-data>
<div class="core:w-full">
<div class="core:flex core:gap-2">
<span class="core:grow" [innerHTML]="data.label"></span>
@if (data.link) {
<a class="core:flex-none core:text-color-secondary" [href]="data.link" target="_blank">
<i class="fa fa-external-link"></i>
</a>
}
</div>
@if (data.summary) {
<div class="core:w-full" [innerHTML]="data.summary" [ngClass]="props.summaryClass"></div>
}
</div>
</ng-template>
</p-autoComplete>
} @else {
<div class="core:flex core:gap-1 core:items-center">
<div [innerHtml]="valueSelected()"></div>
<p-button icon="fa fa-trash" severity="secondary" [text]="true" (onClick)="clear()" />
</div>
}
</div>
`, isInline: true, dependencies: [{ kind: "component", type: i1$3.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "delay", "style", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "disabled", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "maxlength", "name", "required", "size", "appendTo", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "field", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "itemSize", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "variant", "fluid"], outputs: ["completeMethod", "onSelect", "onUnselect", "onFocus", "onBlur", "onDropdownClick", "onClear", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: i4$2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RemoteAutocomplete, decorators: [{
type: Component,
args: [{
selector: 'ng-core-remote-autocomplete',
standalone: false,
template: `
<div style="padding-inline: 0; padding-block: 0" class="core:flex core:gap-1" [ngClass]="{ 'p-inputtext ng-invalid ng-dirty': showError }">
@if (!field.formControl.value) {
@if (props.filters?.options) {
<p-select
[options]="optionValues$|async"
[ngModel]="props.filters.selected"
(onChange)="changeFilter($event)"
>
<ng-template let-selected #selectedItem>
{{ selected.untranslatedLabel | translate }}
</ng-template>
</p-select>
}
<p-autoComplete
class="core:grow"
styleClass="core:w-full"
inputStyleClass="core:w-full"
[scrollHeight]="props.scrollHeight"
[minLength]="props.minLength"
[maxlength]="props.maxLength"
[(ngModel)]="value"
[placeholder]="props.placeholder"
[group]="props.group"
[suggestions]="suggestions()"
(completeMethod)="search($event)"
(onSelect)="onSelect($event)"
>
<ng-template #item let-data>
<div class="core:w-full">
<div class="core:flex core:gap-2">
<span class="core:grow" [innerHTML]="data.label"></span>
@if (data.link) {
<a class="core:flex-none core:text-color-secondary" [href]="data.link" target="_blank">
<i class="fa fa-external-link"></i>
</a>
}
</div>
@if (data.summary) {
<div class="core:w-full" [innerHTML]="data.summary" [ngClass]="props.summaryClass"></div>
}
</div>
</ng-template>
</p-autoComplete>
} @else {
<div class="core:flex core:gap-1 core:items-center">
<div [innerHtml]="valueSelected()"></div>
<p-button icon="fa fa-trash" severity="secondary" [text]="true" (onClick)="clear()" />
</div>
}
</div>
`,
}]
}] });
class NgCoreFormlyRemoteAutocompleteModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyRemoteAutocompleteModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyRemoteAutocompleteModule, declarations: [RemoteAutocomplete], imports: [AutoCompleteModule,
ButtonModule,
CommonModule,
SelectModule,
FormsModule, i5.TranslateModule, i4$1.FormlyModule], exports: [RemoteAutocomplete] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyRemoteAutocompleteModule, imports: [AutoCompleteModule,
ButtonModule,
CommonModule,
SelectModule,
FormsModule,
TranslateModule.forChild(),
FormlyModule.forChild({
types: [
{ name: 'remoteAutoComplete', component: RemoteAutocomplete }
]
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyRemoteAutocompleteModule, decorators: [{
type: NgModule,
args: [{
declarations: [RemoteAutocomplete],
imports: [
AutoCompleteModule,
ButtonModule,
CommonModule,
SelectModule,
FormsModule,
TranslateModule.forChild(),
FormlyModule.forChild({
types: [
{ name: 'remoteAutoComplete', component: RemoteAutocomplete }
]
}),
],
exports: [RemoteAutocomplete]
}]
}] });
;
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class SelectComponent extends FieldType {
constructor() {
super(...arguments);
this.translateService = inject(TranslateService);
this.translateLabelService = inject(TranslateLabelService);
this.ref = inject(ChangeDetectorRef);
this.subscription = new Subscription();
this.filter = false;
/** Default properties */
this.defaultOptions = {
props: {
disabled: false,
editable: false,
filterMatchMode: 'contains',
fluid: true,
group: false,
placeholder: _('Select…'),
required: false,
scrollHeight: CONFIG.DEFAULT_SELECT_SCROLL_HEIGHT,
sort: false,
tooltipPosition: 'top',
tooltipPositionStyle: 'absolute',
}
};
}
ngOnInit() {
const optionsObs = this.props.options;
const changeObs = this.translateService.onLangChange.pipe(switchMap$1(() => this.optionValues$));
if (this.field.props?.disabled) {
this.formControl.disable();
}
this.optionValues$ = merge(...[optionsObs, changeObs])
.pipe(tap$1((options) => {
this.filter = this.enableFilter(options, 0).enabled;
}), map$5(options => {
this.ref.markForCheck();
return this.translateLabelService.translateLabel(options);
}));
}
/**
* Check if the filter feature of the select should be enabled.
*
* This function is interrupted if the threshold is reached.
*
* @param options the list of options
* @param numberOfOptions the numberOfOptions used by the recursion
* @returns enabled: true if the filter should be enabled, the current total of detected options (used by the recursion)
*/
enableFilter(options, numberOfOptions = 0) {
if (!options?.length) {
return { total: numberOfOptions, enabled: false };
}
// number of entries to enable the filter
const limit = 5;
let total = numberOfOptions + options.length;
if (total > limit) {
return { enabled: true, total };
}
for (const option of options) {
const child = this.enableFilter(option?.items, total);
total = child.total;
if (total > limit) {
return { enabled: total > limit, total };
}
}
return { enabled: total > limit, total };
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
clearValidators() {
const { errors } = this.formControl;
this.formControl.setErrors(errors.required ? { required: true } : null);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SelectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SelectComponent, isStandalone: false, selector: "ng-core-primeng-select", usesInheritance: true, ngImport: i0, template: `
@if(props.options) {
<p-select
[appendTo]="props.appendTo"
[class]="props.class"
[dropdownIcon]="props.dropdownIcon"
[editable]="props.editable"
[emptyFilterMessage]="props.emptyFilterMessage"
[emptyMessage]="props.emptyMessage"
[filter]="filter"
filterBy="label"
[filterMatchMode]="props.filterMatchMode"
[fluid]="props.fluid"
[formControl]="formControl"
[formlyAttributes]="field"
[group]="props.group"
[loadingIcon]="props.loadingIcon"
[scrollHeight]="props.scrollHeight"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
[options]="optionValues$|async"
[optionLabel]="props.group ? undefined : 'label'"
[optionValue]="props.group ? undefined : 'value'"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder | translate"
[required]="props.required"
[showClear]="!props.required"
[styleClass]="props.styleClass"
[tooltip]="props.tooltip"
[tooltipPosition]="props.tooltipPosition"
[tooltipPositionStyle]="props.tooltipPositionStyle"
[tooltipStyleClass]="props.tooltipStyleClass"
(onChange)="props.change && props.change(field, $event)"
(onClear)="clearValidators()"
>
<ng-template let-selected #selectedItem>
{{ selected.untranslatedLabel | translate }}
</ng-template>
<ng-template let-item #item>
<span class="core:whitespace-normal">
{{item.label}}
</span>
</ng-template>
<ng-template let-group #group>
@if (group.untranslatedLabel !== 'group-preferred' && group.untranslatedLabel !== 'group-other') {
<div class="core:py-2 core:font-bold">{{ group.label }}</div>
} @else if (group.label === 'group-other') {
<div class="core:py-2"><hr></div>
}
</ng-template>
</p-select>
}
`, isInline: true, dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: i4$2.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SelectComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-primeng-select',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
@if(props.options) {
<p-select
[appendTo]="props.appendTo"
[class]="props.class"
[dropdownIcon]="props.dropdownIcon"
[editable]="props.editable"
[emptyFilterMessage]="props.emptyFilterMessage"
[emptyMessage]="props.emptyMessage"
[filter]="filter"
filterBy="label"
[filterMatchMode]="props.filterMatchMode"
[fluid]="props.fluid"
[formControl]="formControl"
[formlyAttributes]="field"
[group]="props.group"
[loadingIcon]="props.loadingIcon"
[scrollHeight]="props.scrollHeight"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
[options]="optionValues$|async"
[optionLabel]="props.group ? undefined : 'label'"
[optionValue]="props.group ? undefined : 'value'"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder | translate"
[required]="props.required"
[showClear]="!props.required"
[styleClass]="props.styleClass"
[tooltip]="props.tooltip"
[tooltipPosition]="props.tooltipPosition"
[tooltipPositionStyle]="props.tooltipPositionStyle"
[tooltipStyleClass]="props.tooltipStyleClass"
(onChange)="props.change && props.change(field, $event)"
(onClear)="clearValidators()"
>
<ng-template let-selected #selectedItem>
{{ selected.untranslatedLabel | translate }}
</ng-template>
<ng-template let-item #item>
<span class="core:whitespace-normal">
{{item.label}}
</span>
</ng-template>
<ng-template let-group #group>
@if (group.untranslatedLabel !== 'group-preferred' && group.untranslatedLabel !== 'group-other') {
<div class="core:py-2 core:font-bold">{{ group.label }}</div>
} @else if (group.label === 'group-other') {
<div class="core:py-2"><hr></div>
}
</ng-template>
</p-select>
}
`,
standalone: false
}]
}] });
class NgCoreFormlySelectModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlySelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlySelectModule, declarations: [SelectComponent], imports: [CommonModule,
SelectModule,
ReactiveFormsModule, i5.TranslateModule, FormlySelectModule, i4$1.FormlyModule], exports: [SelectComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlySelectModule, imports: [CommonModule,
SelectModule,
ReactiveFormsModule,
TranslateModule.forChild(),
FormlySelectModule,
FormlyModule.forChild({
types: [
{
name: 'select',
component: SelectComponent,
wrappers: ['form-field'],
},
{ name: 'enum', extends: 'select' },
],
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlySelectModule, decorators: [{
type: NgModule,
args: [{
declarations: [SelectComponent],
imports: [
CommonModule,
SelectModule,
ReactiveFormsModule,
TranslateModule.forChild(),
FormlySelectModule,
FormlyModule.forChild({
types: [
{
name: 'select',
component: SelectComponent,
wrappers: ['form-field'],
},
{ name: 'enum', extends: 'select' },
],
}),
],
exports: [SelectComponent]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TreeSelectComponent extends FieldType {
constructor() {
super(...arguments);
this.translateService = inject(TranslateService);
this.translateLabelService = inject(TranslateLabelService);
this.ref = inject(ChangeDetectorRef);
this.subscription = new Subscription();
this.filter = false;
/** Default properties */
this.defaultOptions = {
props: {
filterBy: 'label',
filterInputAutoFocus: true,
fluid: true,
placeholder: 'Select…',
scrollHeight: CONFIG.DEFAULT_SELECT_SCROLL_HEIGHT,
variant: 'outlined'
}
};
this.nodeSelected = undefined;
this.optionValues = [];
}
ngOnInit() {
const optionsObs = this.props.options;
const changeObs = this.translateService.onLangChange.pipe(switchMap$1(() => this.optionValues$));
this.optionValues$ = merge(...[optionsObs, changeObs])
.pipe(tap$1((options) => {
this.filter = this.enableFilter(options, 0).enabled;
}), map$5(options => {
this.ref.markForCheck();
return this.translateLabelService.translateLabel(options);
}));
this.subscription.add(this.optionValues$.subscribe((options) => {
this.optionValues = options;
if (this.field.formControl?.value) {
this.nodeSelected = this.findNodeByValue(options, this.field.formControl?.value);
}
else {
this.nodeSelected = null;
}
}));
this.subscription.add(this.field.formControl?.valueChanges.subscribe((value) => {
if (value) {
this.nodeSelected = this.findNodeByValue(this.optionValues, value);
}
else {
this.nodeSelected = null;
}
}));
}
/**
* Check if the filter feature of the select should be enabled.
*
* This function is interrupted if the threshold is reached.
*
* @param options the list of options
* @param numberOfOptions the numberOfOptions used by the recursion
* @returns enabled: true if the filter should be enabled, the current total of detected options (used by the recursion)
*/
enableFilter(options, numberOfOptions = 0) {
if (!options?.length) {
return { total: numberOfOptions, enabled: false };
}
// number of entries to enable the filter
const limit = 5;
let total = numberOfOptions + options.length;
if (total > limit) {
return { enabled: true, total };
}
for (const option of options) {
const child = this.enableFilter(option?.children, total);
total = child.total;
if (total > limit) {
return { enabled: total > limit, total };
}
}
return { enabled: total > limit, total };
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
setFormValue(event) {
this.formControl.setValue(event.node.data);
}
clearFormValue() {
this.formControl.reset(null);
const { errors } = this.formControl;
this.formControl.setErrors(errors.required ? { required: true } : null);
}
findNodeByValue(node, value, data) {
if (!data) {
node.map((treeNode) => {
if (treeNode.data === value) {
data = treeNode;
}
else if (treeNode.children) {
data = this.findNodeByValue(treeNode.children, value, data);
}
});
}
return data;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TreeSelectComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: TreeSelectComponent, isStandalone: false, selector: "ng-core-tree-select", usesInheritance: true, ngImport: i0, template: `
<p-treeSelect
[class]="props.class"
[containerStyleClass]="props.containerStyleClass"
[disabled]="props.disabled"
[filter]="filter"
[filterBy]="props.filterBy"
[filterInputAutoFocus]="props.filterInputAutoFocus"
[filterPlaceholder]="props.filterPlaceholder"
[fluid]="props.fluid"
[formlyAttributes]="field"
[ngModel]="nodeSelected"
[options]="optionValues"
[panelClass]="props.panelClass"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder | translate"
[showClear]="!props.required"
[variant]="props.variant"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
(onNodeSelect)="setFormValue($event)"
(onNodeUnselect)="clearFormValue()"
(onClear)="clearFormValue()"
>
<ng-template let-item #item>
<span class="core:whitespace-normal">
{{item.label}}
</span>
</ng-template>
</p-treeSelect>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i3$5.TreeSelect, selector: "p-treeSelect, p-treeselect, p-tree-select", inputs: ["inputId", "scrollHeight", "disabled", "metaKeySelection", "variant", "display", "selectionMode", "tabindex", "ariaLabel", "ariaLabelledBy", "placeholder", "panelClass", "panelStyle", "fluid", "panelStyleClass", "containerStyle", "containerStyleClass", "labelStyle", "labelStyleClass", "overlayOptions", "emptyMessage", "appendTo", "filter", "filterBy", "filterMode", "filterPlaceholder", "filterLocale", "filterInputAutoFocus", "propagateSelectionDown", "propagateSelectionUp", "showClear", "resetFilterOnHide", "virtualScroll", "virtualScrollItemSize", "size", "virtualScrollOptions", "autofocus", "options", "showTransitionOptions", "hideTransitionOptions", "loading"], outputs: ["onNodeExpand", "onNodeCollapse", "onShow", "onHide", "onClear", "onFilter", "onFocus", "onBlur", "onNodeUnselect", "onNodeSelect"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TreeSelectComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-tree-select',
template: `
<p-treeSelect
[class]="props.class"
[containerStyleClass]="props.containerStyleClass"
[disabled]="props.disabled"
[filter]="filter"
[filterBy]="props.filterBy"
[filterInputAutoFocus]="props.filterInputAutoFocus"
[filterPlaceholder]="props.filterPlaceholder"
[fluid]="props.fluid"
[formlyAttributes]="field"
[ngModel]="nodeSelected"
[options]="optionValues"
[panelClass]="props.panelClass"
[panelStyleClass]="props.panelStyleClass"
[placeholder]="props.placeholder | translate"
[showClear]="!props.required"
[variant]="props.variant"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
(onNodeSelect)="setFormValue($event)"
(onNodeUnselect)="clearFormValue()"
(onClear)="clearFormValue()"
>
<ng-template let-item #item>
<span class="core:whitespace-normal">
{{item.label}}
</span>
</ng-template>
</p-treeSelect>
`,
standalone: false
}]
}] });
class NgCoreFormlyTreeSelectModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyTreeSelectModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyTreeSelectModule, declarations: [TreeSelectComponent], imports: [CommonModule,
FormsModule,
FormlySelectModule$1, i5.TranslateModule, TreeSelectModule, i4$1.FormlyModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyTreeSelectModule, imports: [CommonModule,
FormsModule,
FormlySelectModule$1,
TranslateModule.forChild(),
TreeSelectModule,
FormlyModule.forChild({
types: [
{
name: 'tree-select',
component: TreeSelectComponent,
wrappers: ['form-field'],
}
],
})] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: NgCoreFormlyTreeSelectModule, decorators: [{
type: NgModule,
args: [{
declarations: [TreeSelectComponent],
imports: [
CommonModule,
FormsModule,
FormlySelectModule$1,
TranslateModule.forChild(),
TreeSelectModule,
FormlyModule.forChild({
types: [
{
name: 'tree-select',
component: TreeSelectComponent,
wrappers: ['form-field'],
}
],
}),
],
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying a label with dropdown in editor.
*/
class DropdownLabelEditorComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DropdownLabelEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: DropdownLabelEditorComponent, isStandalone: false, selector: "ng-core-editor-dropdown-label-editor", inputs: { field: "field", canAdd: "canAdd" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (field.props.label) {\n <div class=\"core:flex core:gap-1 core:items-center\">\n <span [pTooltip]=\"field.props.description\" tooltipPosition=\"top\">\n {{ field.props.label }}\n @if (field.props.required) {\n *\n }\n </span>\n <ng-content select=\"[menu]\"/>\n <ng-content id=\"{field.id}-clone-button\" select=\"[buttons]\"/>\n </div>\n}\n", dependencies: [{ kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DropdownLabelEditorComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-editor-dropdown-label-editor', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (field.props.label) {\n <div class=\"core:flex core:gap-1 core:items-center\">\n <span [pTooltip]=\"field.props.description\" tooltipPosition=\"top\">\n {{ field.props.label }}\n @if (field.props.required) {\n *\n }\n </span>\n <ng-content select=\"[menu]\"/>\n <ng-content id=\"{field.id}-clone-button\" select=\"[buttons]\"/>\n </div>\n}\n" }]
}], propDecorators: { field: [{
type: Input
}], canAdd: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class LabelComponent {
constructor() {
this.translateService = inject(TranslateService);
// Subscriptions to observables.
this.subscriptions = new Subscription();
this.items = [];
}
ngOnInit() {
this.updateItems();
// update items when the hidden state of the children change
const fields = this.getFieldGroup(this.field);
if (fields?.length > 0) {
this.subscriptions.add(merge(...fields.map((child) => child.options.fieldChanges))
.subscribe((changes) => {
if (changes.type === 'hidden') {
this.updateItems();
}
}));
// update translations
this.subscriptions.add(this.translateService.onLangChange.subscribe(() => this.updateItems()));
}
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
updateItems() {
if (this.hasMenu(this.field)) {
this.items = [];
this.hiddenFieldGroup(this.getFieldGroup(this.field)).map((field) => {
if (!field.props.untranslatedLabel) {
field.props.untranslatedLabel = field.props.label;
}
this.items.push({
label: this.translateService.instant(field.props.untranslatedLabel),
command: () => this.show(field),
});
});
}
}
/**
* Is the dropdown menu displayed?
* @param field - FormlyFieldConfig, the corresponding form field config
* @returns boolean, true if the menu should be displayed
*/
hasMenu(field) {
if (field.fieldGroup && field.fieldGroup.length > 0 && field.fieldGroup[0].type === 'multischema') {
return false;
}
if (this.field.fieldGroup == null) {
return false;
}
return ((this.hiddenFieldGroup(this.getFieldGroup(this.field)).length > 0 || this.field.props.helpURL) &&
field.props?.editorConfig?.longMode);
}
/** Get the current field index position in the parent array.
*
* @returns number - the index position in the parent array, null if the parent
* is not an array.
*/
getIndex() {
if (this.field.parent.type === 'array') {
return Number(this.field.key);
}
return null;
}
/** Get the list of children fields of a given field.
*
* This is more complicated for multischema, the formlyField should be extracted
* from the children of children.
*
* @param field FormlyFieldConfig - the field to get the fieldGroup.
* @returns Array of FormlyFieldConfig - the list of the children fields.
*/
getFieldGroup(field) {
let fieldGroup = [];
// multischema has a nested structure object['enum', 'object'], enum is the oneOf select
// and object['object'] are all the possible oneOf entries
if (field.fieldGroup?.length > 0 && field.fieldGroup[0].type === 'multischema') {
const multischemaFieldGroup = field.fieldGroup[0];
// [0] is the enum i.e. (select) for the oneOf
const multischemaEntries = multischemaFieldGroup.fieldGroup[1];
let activeGroups = multischemaEntries.fieldGroup;
// only the active
activeGroups = activeGroups.filter((f) => f.hide === false);
activeGroups.map((group) => (fieldGroup = [...group.fieldGroup, group]));
}
else {
fieldGroup = field.fieldGroup;
}
return fieldGroup;
}
/**
* Filter the fieldGroup to return the list of hidden field.
* @param fieldGroup - FormlyFieldConfig[], the fieldGroup to filter
* @returns FormlyFieldConfig[], the filtered list
*/
hiddenFieldGroup(fieldGroup) {
return fieldGroup.filter((f) => f.hide && !('hide' in f?.expressions));
}
/**
* Am I at the root of the form?
* @returns boolean, true if I'm the root
*/
isRoot() {
return this.field.props?.isRoot || false;
}
/**
* Hide the field
* @param field - FormlyFieldConfig, the field to hide
*/
remove() {
if (this.field.parent.type === 'object') {
this.field.props.setHide ? this.field.props.setHide(this.field, true) : (this.field.hide = true);
}
if (this.field.parent.type === 'array') {
this.field.parent.props.remove(this.getIndex());
}
}
/**
* Show the field
* @param field - FormlyFieldConfig, the field to show
*/
show(field) {
field.props.setHide ? field.props.setHide(field, false) : (field.hide = false);
}
/**
* Is the field can be hidden?
* @returns boolean, true if the field can be hidden
*/
canRemove() {
switch (this.field.parent.type) {
case 'object':
if (!this.field.props?.editorConfig?.longMode) {
return false;
}
return !this.field.props.required && !this.field.hide;
case 'array':
return this.field.parent.props.canRemove();
default:
return false;
}
}
/**
* Is a new item can be added in an array?
* Note that this is true only if the array does not contains items, else the
* clone button should be used.
* @returns boolean, true if a new item can be added
*/
canAddItem() {
if (this.field.type === 'array' && this.field.props.canAdd) {
return this.field.props.canAdd() && this.field.fieldGroup.length === 0;
}
return false;
}
/**
* Add a new item element
* @param field - FormlyFieldConfig, the field to hide
*/
addItem() {
if (this.field.type === 'array') {
return this.field.props.add(0);
}
}
/**
* Is a array item can be cloned?
* @returns boolean, true if the field can be hidden
*/
canAdd() {
if (this.field.parent.type === 'array') {
return this.field.parent.props.canAdd();
}
return false;
}
/**
* Add a array item after the current field.
* @param field - FormlyFieldConfig, the field to hide
*/
add() {
if (this.field.parent.type === 'array') {
const index = this.getIndex() + 1;
return this.field.parent.props.add(index);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LabelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: LabelComponent, isStandalone: false, selector: "ng-core-label-editor", inputs: { field: "field" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (field) {\n<!-- header and menu -->\n@if (!isRoot()) {\n <ng-container *ngTemplateOutlet=\"hasMenu(field) ? menu : title; context: { f: field }\" />\n}\n\n<!-- TEMPLATES -->\n<!-- dropdown menu -->\n<ng-template #menu let-f=\"f\">\n <ng-core-editor-dropdown-label-editor [field]=\"f\" [canAdd]=\"false\">\n <ng-container menu>\n <div class=\"core:flex\">\n <p-button icon=\"fa fa-caret-down\" severity=\"secondary\" [text]=\"true\" (onClick)=\"menu.toggle($event)\" />\n <p-tieredMenu #menu [model]=\"items\" [popup]=\"true\" appendTo=\"body\" />\n </div>\n </ng-container>\n <ng-container buttons>\n @if (canAddItem()) {\n <p-button icon=\"fa fa-plus\" severity=\"secondary\" [text]=\"true\" (onClick)=\"addItem()\" />\n } @if (canAdd()) {\n <p-button\n id=\"{{ f.id }}-clone-button\"\n icon=\"fa fa-clone\"\n severity=\"secondary\"\n [text]=\"true\"\n styleClass=\"core:mr-1\"\n (onClick)=\"add()\"\n />\n }\n <!-- hide button -->\n @if (canRemove()) {\n <p-button\n id=\"{{ f.id }}-remove-button\"\n icon=\"fa fa-trash\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"remove()\"\n />\n }\n </ng-container>\n </ng-core-editor-dropdown-label-editor>\n</ng-template>\n\n<!-- section title -->\n<ng-template #title let-f=\"f\">\n <div class=\"core:flex core:gap-1 core:items-center\">\n @if (f.props.label) {\n <label class=\"p-fieldset-legend-label\" [attr.for]=\"f.id\" [pTooltip]=\"f.props.description\" tooltipPosition=\"top\">\n {{ f.props.label }}\n </label>\n @if (f.props.required && f.props.hideRequiredMarker !== true) { * } @if (canAddItem()) {\n <p-button icon=\"fa fa-plus\" severity=\"secondary\" [text]=\"true\" (onClick)=\"addItem()\" />\n } @if (canAdd()) {\n <p-button id=\"{{ f.id }}-clone-button\" icon=\"fa fa-clone\" [text]=\"true\" severity=\"secondary\" (onClick)=\"add()\" />\n }\n <!-- hide button -->\n @if (canRemove()) {\n <p-button\n id=\"{{ f.id }}-remove-button\"\n icon=\"fa fa-trash\"\n [text]=\"true\"\n severity=\"secondary\"\n (onClick)=\"remove()\"\n />\n } }\n </div>\n</ng-template>\n}\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$6.TieredMenu, selector: "p-tieredMenu, p-tieredmenu, p-tiered-menu", inputs: ["model", "popup", "style", "styleClass", "appendTo", "breakpoint", "autoZIndex", "baseZIndex", "autoDisplay", "showTransitionOptions", "hideTransitionOptions", "id", "ariaLabel", "ariaLabelledBy", "disabled", "tabindex"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DropdownLabelEditorComponent, selector: "ng-core-editor-dropdown-label-editor", inputs: ["field", "canAdd"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LabelComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-label-editor', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (field) {\n<!-- header and menu -->\n@if (!isRoot()) {\n <ng-container *ngTemplateOutlet=\"hasMenu(field) ? menu : title; context: { f: field }\" />\n}\n\n<!-- TEMPLATES -->\n<!-- dropdown menu -->\n<ng-template #menu let-f=\"f\">\n <ng-core-editor-dropdown-label-editor [field]=\"f\" [canAdd]=\"false\">\n <ng-container menu>\n <div class=\"core:flex\">\n <p-button icon=\"fa fa-caret-down\" severity=\"secondary\" [text]=\"true\" (onClick)=\"menu.toggle($event)\" />\n <p-tieredMenu #menu [model]=\"items\" [popup]=\"true\" appendTo=\"body\" />\n </div>\n </ng-container>\n <ng-container buttons>\n @if (canAddItem()) {\n <p-button icon=\"fa fa-plus\" severity=\"secondary\" [text]=\"true\" (onClick)=\"addItem()\" />\n } @if (canAdd()) {\n <p-button\n id=\"{{ f.id }}-clone-button\"\n icon=\"fa fa-clone\"\n severity=\"secondary\"\n [text]=\"true\"\n styleClass=\"core:mr-1\"\n (onClick)=\"add()\"\n />\n }\n <!-- hide button -->\n @if (canRemove()) {\n <p-button\n id=\"{{ f.id }}-remove-button\"\n icon=\"fa fa-trash\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"remove()\"\n />\n }\n </ng-container>\n </ng-core-editor-dropdown-label-editor>\n</ng-template>\n\n<!-- section title -->\n<ng-template #title let-f=\"f\">\n <div class=\"core:flex core:gap-1 core:items-center\">\n @if (f.props.label) {\n <label class=\"p-fieldset-legend-label\" [attr.for]=\"f.id\" [pTooltip]=\"f.props.description\" tooltipPosition=\"top\">\n {{ f.props.label }}\n </label>\n @if (f.props.required && f.props.hideRequiredMarker !== true) { * } @if (canAddItem()) {\n <p-button icon=\"fa fa-plus\" severity=\"secondary\" [text]=\"true\" (onClick)=\"addItem()\" />\n } @if (canAdd()) {\n <p-button id=\"{{ f.id }}-clone-button\" icon=\"fa fa-clone\" [text]=\"true\" severity=\"secondary\" (onClick)=\"add()\" />\n }\n <!-- hide button -->\n @if (canRemove()) {\n <p-button\n id=\"{{ f.id }}-remove-button\"\n icon=\"fa fa-trash\"\n [text]=\"true\"\n severity=\"secondary\"\n (onClick)=\"remove()\"\n />\n } }\n </div>\n</ng-template>\n}\n" }]
}], propDecorators: { field: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying array fields in editor.
*/
class ArrayTypeComponent extends FieldArrayType {
/**
* Component initialization
*/
ngOnInit() {
this.props.remove = this.remove.bind(this);
this.props.add = this.add.bind(this);
this.props.canAdd = this.canAdd.bind(this);
this.props.canRemove = this.canRemove.bind(this);
}
/**
* Can a new element be added?
* @returns boolean, true if a new element can be inserted in the array
*/
canAdd() {
const { maxItems } = this.field.props;
if (maxItems === undefined) {
return true;
}
return this.field.fieldGroup.length < maxItems;
}
/**
* Can an element be removed?
* @returns boolean, true if an element ca be removed
*/
canRemove() {
const { minItems } = this.field.props;
if (minItems === undefined) {
return true;
}
return this.field.fieldGroup.length > minItems;
}
/**
* Remove an element from the array and hide buttons
* @param i - number, the position to remove the element
*/
remove(i) {
super.remove(i);
}
/**
* Add a new element in the array
* @param i - number, the position to add the element
*/
add(i, initialModel) {
super.add(i, initialModel);
this.setFocusInChildren(this.field.fieldGroup[i]);
}
/**
* Set the focus to the first non object, array in children
* Recursive.
* @param field - FormlyFieldConfig, configuration form
*/
setFocusInChildren(field) {
if (field.fieldGroup && field.fieldGroup.length > 0) {
for (const f of field.fieldGroup) {
if (this.setFocusInChildren(f)) {
return true;
}
}
return false;
}
if (!field.hide) {
field.focus = true;
return true;
}
return false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ArrayTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ArrayTypeComponent, isStandalone: false, selector: "ng-core-editor-formly-array-type", usesInheritance: true, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<!-- array title only if the array is empty -->\n\n@if (props.label && field.props.hideLabel !== true) {\n <ng-core-label-editor [field]=\"field\" />\n}\n\n<div>\n @if (showError) {\n <div class=\"text-error core:my-2\">\n <formly-validation-message [field]=\"field\"/>\n </div>\n }\n <!-- for each item -->\n <div [class]=\"props.containerCssClass\">\n @for (f of field.fieldGroup; track f.id) { @if (!f.hide) {\n <div class=\"{{ f.props.itemCssClass }} core:mb-1\" [id]=\"'field-' + f.id\">\n <formly-field [field]=\"f\" />\n </div>\n } }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: i4$1.LegacyFormlyField, selector: "formly-field" }, { kind: "component", type: i4$1.LegacyFormlyValidationMessage, selector: "formly-validation-message" }, { kind: "component", type: LabelComponent, selector: "ng-core-label-editor", inputs: ["field"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ArrayTypeComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-editor-formly-array-type', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<!-- array title only if the array is empty -->\n\n@if (props.label && field.props.hideLabel !== true) {\n <ng-core-label-editor [field]=\"field\" />\n}\n\n<div>\n @if (showError) {\n <div class=\"text-error core:my-2\">\n <formly-validation-message [field]=\"field\"/>\n </div>\n }\n <!-- for each item -->\n <div [class]=\"props.containerCssClass\">\n @for (f of field.fieldGroup; track f.id) { @if (!f.hide) {\n <div class=\"{{ f.props.itemCssClass }} core:mb-1\" [id]=\"'field-' + f.id\">\n <formly-field [field]=\"f\" />\n </div>\n } }\n </div>\n</div>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class MultiSchemaTypeComponent extends FieldType {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MultiSchemaTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: MultiSchemaTypeComponent, isStandalone: false, selector: "ng-core-editor-formly-multi-schema-type", usesInheritance: true, ngImport: i0, template: `
<div [class]="field.parent.props.cssClass">
@if (props.label) {
<legend [pTooltip]="props.description" tooltipPosition="top">{{ props.label }}</legend>
}
@if (showError && formControl.errors) {
<div class="text-error core:my-2">
<formly-validation-message [field]="field" />
</div>
}
@for (f of field.fieldGroup; track f.id) {
<formly-field [field]="f"></formly-field>
}
</div>
`, isInline: true, dependencies: [{ kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: i4$1.LegacyFormlyField, selector: "formly-field" }, { kind: "component", type: i4$1.LegacyFormlyValidationMessage, selector: "formly-validation-message" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MultiSchemaTypeComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-editor-formly-multi-schema-type',
template: `
<div [class]="field.parent.props.cssClass">
@if (props.label) {
<legend [pTooltip]="props.description" tooltipPosition="top">{{ props.label }}</legend>
}
@if (showError && formControl.errors) {
<div class="text-error core:my-2">
<formly-validation-message [field]="field" />
</div>
}
@for (f of field.fieldGroup; track f.id) {
<formly-field [field]="f"></formly-field>
}
</div>
`,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying an object in editor.
*/
class ObjectTypeComponent extends FieldType {
constructor() {
super(...arguments);
this.defaultOptions = {
props: {
containerCssClass: 'core:flex core:flex-col core:gap-2'
}
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ObjectTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ObjectTypeComponent, isStandalone: false, selector: "ng-core-editor-formly-object-type", usesInheritance: true, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (showError) {\n <div class=\"text-error core:my-2\">\n <formly-validation-message [field]=\"field\"/>\n </div>\n}\n<div [class]=\"props.containerCssClass\">\n @for (f of field.fieldGroup; track f.id) {\n <!-- the core:scroll-mt-18 class is added for sticky -->\n <div\n [hidden]=\"f.hide\"\n [class]=\"f.props.itemCssClass\"\n [ngClass]=\"{'core:scroll-mt-18': (props.isRoot && props.editorConfig?.longMode)}\"\n [id]=\"'field-' + f.id\"\n >\n <formly-field [field]=\"f\" />\n </div>\n }\n</div>\n", dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: i4$1.LegacyFormlyField, selector: "formly-field" }, { kind: "component", type: i4$1.LegacyFormlyValidationMessage, selector: "formly-validation-message" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ObjectTypeComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-editor-formly-object-type', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (showError) {\n <div class=\"text-error core:my-2\">\n <formly-validation-message [field]=\"field\"/>\n </div>\n}\n<div [class]=\"props.containerCssClass\">\n @for (f of field.fieldGroup; track f.id) {\n <!-- the core:scroll-mt-18 class is added for sticky -->\n <div\n [hidden]=\"f.hide\"\n [class]=\"f.props.itemCssClass\"\n [ngClass]=\"{'core:scroll-mt-18': (props.isRoot && props.editorConfig?.longMode)}\"\n [id]=\"'field-' + f.id\"\n >\n <formly-field [field]=\"f\" />\n </div>\n }\n</div>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2022-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;
class PasswordGeneratorTypeComponent extends FieldType {
constructor() {
super(...arguments);
this.httpClient = inject(HttpClient);
this.clipboard = inject(Clipboard);
this.cd = inject(ChangeDetectorRef);
this.passwordField = viewChild('password');
/** Default options */
this.defaultOptions = {
props: {
// Allows you to edit the password
enabledEditMode: false,
// Minimum password size
minLength: 8,
// Maximum password size
maxLength: 255,
// Password generation with special characters (ex: $%&)
specialChar: false,
// The field is readonly
readonly: true,
// If you use the function in javascript, it is possible
// to pass options. See the information above.
generateOptions: {}
}
};
this.hasBeenGenerated = false;
/** Field password type (show or hide password) */
this.type = 'password';
/** Password Observable */
this._password$ = new Subject();
}
/** OnInit hook */
ngOnInit() {
this._password$.pipe(tap((password) => this.formControl.setValue(password)), tap((password) => this.clipboard.copy(this.formControl.errors ? ' ' : password))).subscribe(() => {
this.hasBeenGenerated = true;
this.cd.markForCheck();
});
}
/** Generation of the password when the button is clicked. */
onClick() {
if (!this.props.api) {
this._jsGeneration();
}
else {
this._callApi();
}
}
/**
* Change the password when entering.
* @param password - string
*/
onChange(password) {
this._password$.next(password);
}
/** Hide or show password data */
showHidePassword() {
this.type = this.type === 'password' ? 'text' : 'password';
}
onEdit() {
this.props.readonly = !this.props.readonly;
if (!this.props.readonly) {
this.passwordField().nativeElement.focus();
}
}
/** Generate the password by javascript */
_jsGeneration() {
if (!this.props.generateOptions.length) {
this.props.generateOptions.length = this.props.minLength;
}
if (!this.props.generateOptions.symbols) {
this.props.generateOptions.symbols = this.props.specialChar;
}
this._password$.next(GeneratePassword(this.props.generateOptions));
}
/** Call backend api entrypoint */
_callApi() {
this.httpClient.get(this.props.api, {
responseType: 'text',
params: {
length: this.props.minLength,
special_char: this.props.specialChar
}
}).subscribe((password) => this._password$.next(password));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordGeneratorTypeComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: PasswordGeneratorTypeComponent, isStandalone: false, selector: "ng-core-editor-field-password-generator", viewQueries: [{ propertyName: "passwordField", first: true, predicate: ["password"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: `
<p-inputGroup>
<input
pInputText
#password
id="password"
[type]="type"
autocomplete="off"
[formControl]="formControl"
(change)="onChange($event.target.value)"
[readonly]="props.readonly"
[class]="props.class ? props.class : props.readonly ? 'core:!bg-surface-200' : ''"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
<p-inputGroupAddon class="core:flex">
<div class="core:flex core:justify-center core:items-center core:min-h-full core:grow core:cursor-pointer core:hover:bg-emphasis" (click)="onClick()">
<i class="fa fa-repeat" [title]="'Generate a new password' | translate"></i>
</div>
</p-inputGroupAddon>
<p-inputGroupAddon>
<div class="core:flex core:justify-center core:items-center core:min-h-full core:grow core:cursor-pointer core:hover:bg-emphasis" (click)="showHidePassword()">
<i class="fa" [ngClass]="{'fa-eye': type === 'password', 'fa-eye-slash': type === 'text'}" [title]="'Show or hide password' | translate"></i>
</div>
</p-inputGroupAddon>
@if (props.enabledEditMode) {
<p-inputGroupAddon>
<div class="core:flex core:justify-center core:items-center core:min-h-full core:grow core:cursor-pointer core:hover:bg-emphasis" (click)="onEdit()">
<i class="fa" [ngClass]="{'fa-lock': props.readonly, 'fa-unlock-alt': !props.readonly}" [title]="'Edit mode' | translate"></i>
</div>
</p-inputGroupAddon>
}
</p-inputGroup>
@if (!showError && hasBeenGenerated) {
<div class="core:text-muted-color core:my-4" translate>
The password has been copied to the clipboard.
</div>
}
`, isInline: true, styles: ["p-inputGroupAddon{padding:0!important}\n"], dependencies: [{ kind: "component", type: i3.InputGroupAddon, selector: "p-inputgroup-addon, p-inputGroupAddon", inputs: ["style", "styleClass"] }, { kind: "component", type: i4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["style", "styleClass"] }, { kind: "directive", type: i3$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i5.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i3$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: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PasswordGeneratorTypeComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-editor-field-password-generator', template: `
<p-inputGroup>
<input
pInputText
#password
id="password"
[type]="type"
autocomplete="off"
[formControl]="formControl"
(change)="onChange($event.target.value)"
[readonly]="props.readonly"
[class]="props.class ? props.class : props.readonly ? 'core:!bg-surface-200' : ''"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
/>
<p-inputGroupAddon class="core:flex">
<div class="core:flex core:justify-center core:items-center core:min-h-full core:grow core:cursor-pointer core:hover:bg-emphasis" (click)="onClick()">
<i class="fa fa-repeat" [title]="'Generate a new password' | translate"></i>
</div>
</p-inputGroupAddon>
<p-inputGroupAddon>
<div class="core:flex core:justify-center core:items-center core:min-h-full core:grow core:cursor-pointer core:hover:bg-emphasis" (click)="showHidePassword()">
<i class="fa" [ngClass]="{'fa-eye': type === 'password', 'fa-eye-slash': type === 'text'}" [title]="'Show or hide password' | translate"></i>
</div>
</p-inputGroupAddon>
@if (props.enabledEditMode) {
<p-inputGroupAddon>
<div class="core:flex core:justify-center core:items-center core:min-h-full core:grow core:cursor-pointer core:hover:bg-emphasis" (click)="onEdit()">
<i class="fa" [ngClass]="{'fa-lock': props.readonly, 'fa-unlock-alt': !props.readonly}" [title]="'Edit mode' | translate"></i>
</div>
</p-inputGroupAddon>
}
</p-inputGroup>
@if (!showError && hasBeenGenerated) {
<div class="core:text-muted-color core:my-4" translate>
The password has been copied to the clipboard.
</div>
}
`, standalone: false, styles: ["p-inputGroupAddon{padding:0!important}\n"] }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying a switcher in editor.
*/
class SwitchComponent extends FieldType {
constructor() {
super(...arguments);
/** Default properties */
this.defaultOptions = {
props: {
hideLabel: true,
},
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SwitchComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SwitchComponent, isStandalone: false, selector: "ng-core-editor-formly-field-switch", usesInheritance: true, ngImport: i0, template: `
<div class="core:flex core:gap-2 core:my-2">
<p-toggleswitch
[ngClass]="{'ng-invalid ng-dirty': showError }"
[formControl]="formControl"
[formlyAttributes]="field"
/>
<label [for]="id" [pTooltip]="props.description" tooltipPosition="top">{{ props.label }}</label>
</div>
`, isInline: true, dependencies: [{ kind: "component", type: i1$4.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["style", "styleClass", "tabindex", "inputId", "name", "disabled", "readonly", "trueValue", "falseValue", "ariaLabel", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SwitchComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-editor-formly-field-switch',
template: `
<div class="core:flex core:gap-2 core:my-2">
<p-toggleswitch
[ngClass]="{'ng-invalid ng-dirty': showError }"
[formControl]="formControl"
[formlyAttributes]="field"
/>
<label [for]="id" [pTooltip]="props.description" tooltipPosition="top">{{ props.label }}</label>
</div>
`,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2022-2024 RERO
* Copyright (C) 2022 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;
class ExportButtonComponent {
constructor() {
/** Export formats configuration */
this.exportOptions = input([]);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ExportButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ExportButtonComponent, isStandalone: false, selector: "ng-core-export-button", inputs: { exportOptions: { classPropertyName: "exportOptions", publicName: "exportOptions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2022-2025 RERO\n Copyright (C) 2022 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (exportOptions() && exportOptions().length > 0) {\n <!-- check single export format -->\n @if (exportOptions().length === 1) {\n @if (!exportOptions()[0]?.disabled) {\n <a\n pButton\n outlined\n severity=\"secondary\"\n [href]=\"exportOptions()[0].url\"\n rel=\"noopener noreferrer\"\n >\n <i class=\"fa fa-cloud-download core:mr-1\"></i>\n {{ 'Export as' | translate }} {{ exportOptions()[0].label }}\n </a>\n } @else {\n <p-button\n severity=\"secondary\"\n outlined\n label=\"{{ 'Export as' | translate }} {{ exportOptions()[0].label }}\"\n icon=\"fa fa-cloud-download\"\n [href]=\"exportOptions()[0].url\"\n [disabled]=\"exportOptions()[0]?.disabled\"\n [pTooltip]=\"exportOptions()[0]?.disabled_message\"\n [tooltipDisabled]=\"!exportOptions()[0]?.disabled\"\n tooltipPosition=\"top\"\n />\n }\n } @else {\n <!-- display multiple export formats as dropdown list -->\n <p-menu #menu [model]=\"exportOptions()\" [popup]=\"true\" />\n <p-button\n severity=\"secondary\"\n outlined\n [disabled]=\"exportOptions()[0]?.disabled\"\n [pTooltip]=\"exportOptions()[0]?.disabled_message\"\n [tooltipDisabled]=\"!exportOptions()[0]?.disabled\"\n tooltipPosition=\"top\"\n (onClick)=\"menu.toggle($event)\"\n >\n <div class=\"core:flex core:gap-4 core:items-center\">\n <i class=\"fa fa-download\"></i>\n {{ 'Export as' | translate }} \u2026\n <i class=\"fa fa-caret-down\"></i>\n </div>\n </p-button>\n }\n}\n", dependencies: [{ kind: "directive", type: i1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$7.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "appendTo", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ExportButtonComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-export-button', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2022-2025 RERO\n Copyright (C) 2022 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (exportOptions() && exportOptions().length > 0) {\n <!-- check single export format -->\n @if (exportOptions().length === 1) {\n @if (!exportOptions()[0]?.disabled) {\n <a\n pButton\n outlined\n severity=\"secondary\"\n [href]=\"exportOptions()[0].url\"\n rel=\"noopener noreferrer\"\n >\n <i class=\"fa fa-cloud-download core:mr-1\"></i>\n {{ 'Export as' | translate }} {{ exportOptions()[0].label }}\n </a>\n } @else {\n <p-button\n severity=\"secondary\"\n outlined\n label=\"{{ 'Export as' | translate }} {{ exportOptions()[0].label }}\"\n icon=\"fa fa-cloud-download\"\n [href]=\"exportOptions()[0].url\"\n [disabled]=\"exportOptions()[0]?.disabled\"\n [pTooltip]=\"exportOptions()[0]?.disabled_message\"\n [tooltipDisabled]=\"!exportOptions()[0]?.disabled\"\n tooltipPosition=\"top\"\n />\n }\n } @else {\n <!-- display multiple export formats as dropdown list -->\n <p-menu #menu [model]=\"exportOptions()\" [popup]=\"true\" />\n <p-button\n severity=\"secondary\"\n outlined\n [disabled]=\"exportOptions()[0]?.disabled\"\n [pTooltip]=\"exportOptions()[0]?.disabled_message\"\n [tooltipDisabled]=\"!exportOptions()[0]?.disabled\"\n tooltipPosition=\"top\"\n (onClick)=\"menu.toggle($event)\"\n >\n <div class=\"core:flex core:gap-4 core:items-center\">\n <i class=\"fa fa-download\"></i>\n {{ 'Export as' | translate }} \u2026\n <i class=\"fa fa-caret-down\"></i>\n </div>\n </p-button>\n }\n}\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2023 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Class representing a record set returned by API.
*/
class Record {
}
class SearchResult {
}
function emailValidator(field) {
// Regex pattern to check email
const emailPattern = /^([A-Za-z0-9\._%+-]+@[A-Za-z0-9\.-]+\.[A-Z|a-z]{2,})$/;
if (field.props?.type !== 'email' || field.validators?.email) {
return;
}
field.validators = field.validators || {};
field.validators.email = {
expression: (control) => control.value ? (new RegExp(emailPattern)).test(control.value) : true,
message: `This value is not a valid email.`,
};
}
/*
* RERO angular core
* Copyright (C) 2021-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component to display textarea as a markdown editor.
*/
class MarkdownFieldComponent extends FieldType {
constructor() {
super(...arguments);
/** Default options */
this.defaultOptions = {
props: {
minHeight: '500px',
promptURLs: true,
spellChecker: false,
status: false,
toolbar: [
'bold',
'italic',
'heading',
'|',
'unordered-list',
'ordered-list',
'|',
'link',
'image',
'|',
'preview',
'fullscreen',
'|',
'guide',
]
}
};
}
/**
* Markdown editor initialization and listen for changes to update the model
* value.
*/
ngAfterViewInit() {
const mde = new EasyMDE({
spellChecker: this.props.spellChecker,
promptURLs: this.props.promptURLs,
initialValue: this.formControl.value,
maxHeight: this.props.maxHeight,
minHeight: this.props.minHeight,
toolbar: this.props.toolbar,
element: this.textarea.nativeElement,
status: this.props.status,
});
mde.codemirror.on('change', () => {
this.formControl.patchValue(mde.value());
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MarkdownFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: MarkdownFieldComponent, isStandalone: false, selector: "ng-core-editor-formly-field-markdown", viewQueries: [{ propertyName: "textarea", first: true, predicate: ["textarea"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
<div style="padding-inline: 0; padding-block: 0" [ngClass]="{ 'p-inputtext ng-invalid ng-dirty': showError }">
<textarea pTextarea #textarea></textarea>
</div>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$5.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MarkdownFieldComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-editor-formly-field-markdown',
template: `
<div style="padding-inline: 0; padding-block: 0" [ngClass]="{ 'p-inputtext ng-invalid ng-dirty': showError }">
<textarea pTextarea #textarea></textarea>
</div>
`,
standalone: false
}]
}], propDecorators: { textarea: [{
type: ViewChild,
args: ['textarea']
}] } });
/*
* RERO angular core
* Copyright (C) 2022-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;
;
class RadioButtonComponent extends FieldType {
constructor() {
super(...arguments);
this.defaultOptions = {
props: {
options: [],
style: 'stacked'
}
};
}
get disabledControl() {
return new FormControl({ value: this.formControl.value, disabled: true });
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadioButtonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: RadioButtonComponent, isStandalone: false, selector: "ng-core-editor-field-radio-button", usesInheritance: true, ngImport: i0, template: `
<ng-template #radioButton let-option="option" let-class="class">
<div class="core:flex">
<p-radiobutton
[inputId]="option.value"
[formControl]="option.disabled ? disabledControl : formControl"
[value]="option.value"
/>
<label [for]="option.value" class="core:ml-1">{{ option.untranslatedLabel | translate }}</label>
</div>
</ng-template>
@if (props.style === 'stacked') {
@for (option of props.options | async; track option.value) {
<ng-container [ngTemplateOutlet]="radioButton" [ngTemplateOutletContext]="{option}"/>
}
} @else {
<div class="core:flex core:gap-2">
@for (option of props.options | async; track option.value) {
<ng-container [ngTemplateOutlet]="radioButton" [ngTemplateOutletContext]="{option}"/>
}
</div>
}
`, isInline: true, dependencies: [{ kind: "component", type: i1$6.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "formControlName", "name", "disabled", "variant", "size", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "style", "styleClass", "autofocus", "binary"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RadioButtonComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-editor-field-radio-button',
template: `
<ng-template #radioButton let-option="option" let-class="class">
<div class="core:flex">
<p-radiobutton
[inputId]="option.value"
[formControl]="option.disabled ? disabledControl : formControl"
[value]="option.value"
/>
<label [for]="option.value" class="core:ml-1">{{ option.untranslatedLabel | translate }}</label>
</div>
</ng-template>
@if (props.style === 'stacked') {
@for (option of props.options | async; track option.value) {
<ng-container [ngTemplateOutlet]="radioButton" [ngTemplateOutletContext]="{option}"/>
}
} @else {
<div class="core:flex core:gap-2">
@for (option of props.options | async; track option.value) {
<ng-container [ngTemplateOutlet]="radioButton" [ngTemplateOutletContext]="{option}"/>
}
</div>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class TextareaFieldComponent extends FieldType {
constructor() {
super(...arguments);
/** Default properties */
this.defaultOptions = {
props: {
displayChars: false,
displayWords: false,
}
};
}
/**
* Get the number of chars.
*
* @returns The number of chars.
*/
get countChars() {
return this.formControl.value ? this.formControl.value.length : 0;
}
/**
* Get the number of words.
*
* @returns The number of words.
*/
get countWords() {
return this.formControl.value ? this.formControl.value.split(/\s+/).length : 0;
}
/**
* Component init.
*
* Adds validator for chars and words.
* Check if the current value does not exceed the limit value for chars or words.
*/
ngOnInit() {
if (this.props.limitWords || this.props.limitChars) {
this.formControl.setValidators([this.limitValidator(), this.formControl.validator]);
this.formControl.updateValueAndValidity();
}
}
/**
* Form validator to check if the value is not greater than the limit.
*
* @returns A validator function returning the eventual error.
*/
limitValidator() {
return (control) => {
if (this.props.limitWords && this.countWords > this.props.limitWords) {
return { limitWords: { value: control.value } };
}
if (this.props.limitChars && this.countChars > this.props.limitChars) {
return { limitChars: { value: control.value } };
}
return null;
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TextareaFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: TextareaFieldComponent, isStandalone: false, selector: "ng-core-editor-formly-field-textarea", usesInheritance: true, ngImport: i0, template: `
<textarea
pTextarea
[formControl]="formControl"
[cols]="props.cols"
[rows]="props.rows"
class="core:w-full"
[class.is-invalid]="showError"
[formlyAttributes]="field"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
></textarea>
@if (field.props.limitWords || field.props.displayWords) {
<ng-container
[ngTemplateOutlet]="counter"
[ngTemplateOutletContext]="{
limit: field.props.limitWords,
count: countWords,
label: 'Number of words' | translate
}"></ng-container>
}
@if (field.props.limitChars || field.props.displayChars) {
<ng-container
[ngTemplateOutlet]="counter"
[ngTemplateOutletContext]="{
limit: field.props.limitChars,
count: countChars,
label: 'Number of chars' | translate
}"></ng-container>
}
<ng-template #counter let-limit="limit" let-count="count" let-label="label">
<span class="core:text-sm core:text-muted-color core:inline-block core:mr-4">
{{ label }}: {{ count }}
@if (limit) {
/ {{ limit }}
}
</span>
</ng-template>
`, isInline: true, dependencies: [{ kind: "directive", type: i1$5.Textarea, selector: "[pTextarea], [pInputTextarea]", inputs: ["autoResize", "variant", "fluid", "pSize"], outputs: ["onResize"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3$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: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i4$1.LegacyFormlyAttributes, selector: "[formlyAttributes]" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TextareaFieldComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-editor-formly-field-textarea',
template: `
<textarea
pTextarea
[formControl]="formControl"
[cols]="props.cols"
[rows]="props.rows"
class="core:w-full"
[class.is-invalid]="showError"
[formlyAttributes]="field"
[ngClass]="{ 'ng-invalid ng-dirty': showError }"
></textarea>
@if (field.props.limitWords || field.props.displayWords) {
<ng-container
[ngTemplateOutlet]="counter"
[ngTemplateOutletContext]="{
limit: field.props.limitWords,
count: countWords,
label: 'Number of words' | translate
}"></ng-container>
}
@if (field.props.limitChars || field.props.displayChars) {
<ng-container
[ngTemplateOutlet]="counter"
[ngTemplateOutletContext]="{
limit: field.props.limitChars,
count: countChars,
label: 'Number of chars' | translate
}"></ng-container>
}
<ng-template #counter let-limit="limit" let-count="count" let-label="label">
<span class="core:text-sm core:text-muted-color core:inline-block core:mr-4">
{{ label }}: {{ count }}
@if (limit) {
/ {{ limit }}
}
</span>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class CardWrapperComponent extends FieldWrapper {
/** OnInit hook */
ngOnInit() {
// remove the field label as it is displayed by this wrapper
this.field.props.hideLabel = true;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CardWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: CardWrapperComponent, isStandalone: false, selector: "ng-core-card-wrapper", usesInheritance: true, ngImport: i0, template: `
@if (!props?.isRoot) {
<p-fieldset>
<ng-template #header>
<ng-core-label-editor [field]="field" />
</ng-template>
<ng-template #fieldComponent></ng-template>
</p-fieldset>
} @else {
<ng-template #fieldComponent></ng-template>
}
`, isInline: true, dependencies: [{ kind: "component", type: i1$7.Fieldset, selector: "p-fieldset", inputs: ["legend", "toggleable", "collapsed", "style", "styleClass", "transitionOptions"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "component", type: LabelComponent, selector: "ng-core-label-editor", inputs: ["field"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CardWrapperComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-card-wrapper',
template: `
@if (!props?.isRoot) {
<p-fieldset>
<ng-template #header>
<ng-core-label-editor [field]="field" />
</ng-template>
<ng-template #fieldComponent></ng-template>
</p-fieldset>
} @else {
<ng-template #fieldComponent></ng-template>
}
`,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class FormFieldWrapperComponent extends FieldWrapper {
/** Hide the field */
remove() {
switch (this.field.parent.type) {
case 'object':
this.field.props.setHide ? this.field.props.setHide(this.field, true) : this.field.hide = true;
break;
case 'array':
this.field.parent.props.remove(Number(this.field.key));
break;
}
}
/**
* Is the field can be hidden?
* @returns boolean, true if the field can be hidden
*/
canRemove() {
switch (this.field.parent.type) {
case 'object':
if (!this.field.props?.editorConfig?.longMode) {
return false;
}
return (!this.field.props.required && !this.field.hide);
case 'array':
return this.field.parent.props.canRemove();
default:
return false;
}
}
/**
* Is the field can be hidden?
* @returns boolean, true if the field can be hidden
*/
canAdd() {
if (this.field.parent.type === 'array') {
return this.field.parent.props.canAdd();
}
return false;
}
/** Add a new element */
add() {
if (this.field.parent.type === 'array') {
const index = Number(this.field.key) + 1;
this.field.parent.props.add(index);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormFieldWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: FormFieldWrapperComponent, isStandalone: false, selector: "ng-core-form-field-wrapper", usesInheritance: true, ngImport: i0, template: `
<div class="core:flex core:flex-col core:gap-2" [class]="props.cssClass" [class.has-error]="showError">
<!-- label -->
@if (props.label && props.hideLabel !== true) {
<div>
<label [attr.for]="id" [pTooltip]="props.description" tooltipPosition="top">
{{ props.label }}
@if (props.required && props.hideRequiredMarker !== true) {
*
}
</label>
</div>
}
<div class="core:flex core:content-center core:grow core:gap-1">
<div class="core:grow">
<!-- field -->
<ng-template #fieldComponent></ng-template>
</div>
@if (canAdd() || canRemove()) {
<div class="core:flex core:gap-1 core:items-center">
<!-- clone button -->
@if (canAdd()) {
<p-button icon="fa fa-clone" severity="secondary" [text]="true" (onClick)="add()" />
}
<!-- trash button -->
@if (canRemove()) {
<p-button icon="fa fa-trash" severity="secondary" [text]="true" (onClick)="remove()" />
}
</div>
}
</div>
@if (showError) {
<div class="text-error core:my-2">
<formly-validation-message [field]="field"/>
</div>
}
</div>
`, isInline: true, dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: i4$1.LegacyFormlyValidationMessage, selector: "formly-validation-message" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FormFieldWrapperComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-form-field-wrapper',
template: `
<div class="core:flex core:flex-col core:gap-2" [class]="props.cssClass" [class.has-error]="showError">
<!-- label -->
@if (props.label && props.hideLabel !== true) {
<div>
<label [attr.for]="id" [pTooltip]="props.description" tooltipPosition="top">
{{ props.label }}
@if (props.required && props.hideRequiredMarker !== true) {
*
}
</label>
</div>
}
<div class="core:flex core:content-center core:grow core:gap-1">
<div class="core:grow">
<!-- field -->
<ng-template #fieldComponent></ng-template>
</div>
@if (canAdd() || canRemove()) {
<div class="core:flex core:gap-1 core:items-center">
<!-- clone button -->
@if (canAdd()) {
<p-button icon="fa fa-clone" severity="secondary" [text]="true" (onClick)="add()" />
}
<!-- trash button -->
@if (canRemove()) {
<p-button icon="fa fa-trash" severity="secondary" [text]="true" (onClick)="remove()" />
}
</div>
}
</div>
@if (showError) {
<div class="text-error core:my-2">
<formly-validation-message [field]="field"/>
</div>
}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class HideWrapperComponent extends FieldWrapper {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: HideWrapperComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: HideWrapperComponent, isStandalone: false, selector: "ng-core-hide-wrapper", usesInheritance: true, ngImport: i0, template: '', isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: HideWrapperComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-hide-wrapper',
// do not include the children
template: '',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Service for managing records search.
*/
class RecordSearchService {
/**
* Constructor, initialize aggregations filters subject.
*/
constructor() {
/** Aggregations filters array */
this._aggregationsFilters = null;
this._aggregationsFiltersSubject = new BehaviorSubject(this._aggregationsFilters);
}
/**
* Returns an observable which emits aggregations filters.
*/
get aggregationsFilters() {
return this._aggregationsFiltersSubject.asObservable();
}
/**
* Removes the given value from selected filters and removes all children
* selected values, too.
* @param key Aggregation key
* @param bucket Bucket containing the value to remove
*/
removeAggregationFilter(key, bucket) {
this.removeFilter(key, bucket.key.toString());
this.removeChildrenFilters(bucket);
this.removeParentFilters(bucket);
// Update selected aggregations filters
this._aggregationsFiltersSubject.next(cloneDeep(this._aggregationsFilters));
}
/**
* Set all aggregations filters and emit the list.
* @param aggregationsFilters List of aggregations filters
* @param forceRefresh Force the refresh of aggregations filters
*/
setAggregationsFilters(aggregationsFilters, forceRefresh = false) {
// TODO: If the filter is in a child bucket, checks if all parents are
// selected, too. If not, adds all parents filters.
if (!forceRefresh && JSON.stringify(this._aggregationsFilters) !== JSON.stringify(aggregationsFilters)) {
forceRefresh = true;
}
if (forceRefresh) {
this._aggregationsFilters = cloneDeep(aggregationsFilters);
this._aggregationsFiltersSubject.next(cloneDeep(this._aggregationsFilters));
}
}
/**
* Stores selected values for an aggregation or removes it if values are empty.
* @param term Term (aggregation key)
* @param values Selected values
* @param bucket The complete ElasticSearch affected bucket configuration
*/
updateAggregationFilter(term, values, bucket = null) {
if (this._aggregationsFilters === null) {
this._aggregationsFilters = [];
}
if (bucket) {
this.removeParentFilters(bucket);
this.removeChildrenFilters(bucket);
}
if (values.length === 0) {
// no more items selected, remove filter
this._aggregationsFilters = this._aggregationsFilters.filter(item => item.key !== term);
}
else {
const filter = this._aggregationsFilters.find(item => item.key === term);
// In both cases values are affected with destructuring assignment, to
// avoid values to be modified outside the service, as array are assigned
// by reference.
if (filter) {
// update existing filter
filter.values = [...values];
}
else {
// add new filter
this._aggregationsFilters.push({ key: term, values: [...values] });
}
}
this._aggregationsFiltersSubject.next(cloneDeep(this._aggregationsFilters));
}
/**
* Remove a filter from the current aggregations filters.
* @param key - the filter name
* @param value - the filter value
* @param emit - is the `_aggregationsFiltersSubject` should be emitted
*/
removeFilter(key, value, emit = false) {
const filter = this._aggregationsFilters.find(item => item.key === key);
if (filter) {
filter.values = filter.values.filter(item => item !== value);
// No more selected values, remove key from aggregations filters.
if (filter.values.length === 0) {
this._aggregationsFilters = this._aggregationsFilters.filter(item => item.key !== filter.key);
}
}
if (emit) {
this._aggregationsFiltersSubject.next(cloneDeep(this._aggregationsFilters));
}
}
/**
* Check if a given filter exist in the current aggregations filters.
* @param key - the filter name
* @param value - the filter value
* @returns `true` if exists
*/
hasFilter(key, value) {
const filter = this._aggregationsFilters.find((a) => a.key === key);
return !!(filter && filter.values.some(v => v === value));
}
/**
* Remove the parent filters of a given bucket.
* @param bucket - The ElasticSearch bucket
*/
removeParentFilters(bucket) {
if (bucket.parent) {
this.removeParentFilters(bucket.parent);
}
this.removeFilter(bucket.aggregationKey, bucket.key);
}
/**
* Removes children filters of a given bucket.
* @param bucket - The ElasticSearch bucket
*/
removeChildrenFilters(bucket) {
for (const k of Object.keys(bucket).filter(key => bucket[key].buckets)) {
const aggregationsKey = k;
for (const childBucket of bucket[k].buckets) {
this.removeFilter(aggregationsKey, childBucket.key);
this.removeChildrenFilters(childBucket);
}
}
}
/**
* Check if a children filter of a given bucket exists
* @param bucket - The ElasticSearch bucket
* @returns `true` if the bucket has children with a corresponding filter
*/
hasChildrenFilter(bucket) {
for (const k of Object.keys(bucket).filter(key => typeof bucket[key] === 'object' && bucket[key].buckets)) {
const aggregationsKey = k;
for (const childBucket of bucket[k].buckets) {
const filter = this._aggregationsFilters.find((v) => v.key === aggregationsKey);
if ((filter && filter.values.includes(childBucket.key)) || this.hasChildrenFilter(childBucket)) {
return true;
}
}
}
return false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [] });
/*
* RERO angular core
* Copyright (C) 2020-20224 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Component for displaying record as JSON in brief views.
*/
class JsonComponent {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JsonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: JsonComponent, isStandalone: false, selector: "ng-component", inputs: { record: "record", type: "type", detailUrl: "detailUrl" }, ngImport: i0, template: `
{{ record|json }}
@if (detailUrl) {
<div class="core:my-4">
@if (detailUrl.external === false) {
<p-button outlined icon="pi pi-eye" [label]="'Show' | translate" [routerLink]="detailUrl.link" />
} @else {
<p-button outlined icon="pi pi-eye" [label]="'Show' | translate" [href]="detailUrl.link" />
}
</div>
}
`, isInline: true, dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2$8.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: i1$2.JsonPipe, name: "json" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: JsonComponent, decorators: [{
type: Component,
args: [{
template: `
{{ record|json }}
@if (detailUrl) {
<div class="core:my-4">
@if (detailUrl.external === false) {
<p-button outlined icon="pi pi-eye" [label]="'Show' | translate" [routerLink]="detailUrl.link" />
} @else {
<p-button outlined icon="pi pi-eye" [label]="'Show' | translate" [href]="detailUrl.link" />
}
</div>
}
`,
standalone: false
}]
}], propDecorators: { record: [{
type: Input
}], type: [{
type: Input
}], detailUrl: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2019-2024 RERO
* Copyright (C) 2019-2023 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordSearchResultComponent {
constructor() {
this.changeDetectorRef = inject(ChangeDetectorRef);
this.router = inject(Router);
// Store current URL to come back to the same page
this.currentUrl = null;
// Admin mode (edit, remove, add, ...)
this.adminMode = { can: false, message: '' };
// Observable emitting current value of record URL.
this.detailUrl$ = null;
// Event emitted when a record is deleted
this.deletedRecord = new EventEmitter();
this.deleteMessageEvent = new EventEmitter();
}
// Return a message containing the reasons why the item cannot be deleted
get deleteInfoMessage() {
return this.deleteStatus.message;
}
// OnInit hook
ngOnInit() {
this.currentUrl = window.location.href;
if (this.canDelete$) {
this.canDelete$.subscribe((result) => {
this.deleteStatus = result;
});
}
if (this.canUpdate$) {
this.canUpdate$.subscribe((result) => {
this.updateStatus = result;
});
}
if (this.canUse$) {
this.canUse$.subscribe((result) => {
this.useStatus = result;
});
}
if (this.detailUrl$) {
this.detailUrl$.subscribe((url) => {
this.detailUrl = url;
});
}
}
// AfterViewInit hook
ngAfterViewInit() {
this.container.clear();
const component = this.container.createComponent(this.itemViewComponent || JsonComponent);
component.instance.record = this.record;
component.instance.type = this.type;
component.instance.detailUrl = this.detailUrl;
this.changeDetectorRef.detectChanges();
}
/**
* Delete a record
* @param pid - string, pid to delete
* @param type - string, resource type
*/
deleteRecord(pid, type) {
if (this.deleteStatus.can) {
this.deletedRecord.emit({ pid, type });
}
else {
this.deleteMessageEvent.emit(this.deleteStatus.message.replace(new RegExp('\n', 'g'), '<br>'));
}
}
/**
* Use a record
*/
useRecord() {
this.router.navigateByUrl(this.useStatus.url);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchResultComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: RecordSearchResultComponent, isStandalone: false, selector: "ng-core-record-search-result", inputs: { record: "record", type: "type", itemViewComponent: "itemViewComponent", adminMode: "adminMode", canUpdate$: "canUpdate$", canDelete$: "canDelete$", canUse$: "canUse$", aggregations: "aggregations", detailUrl$: "detailUrl$" }, outputs: { deletedRecord: "deletedRecord", deleteMessageEvent: "deleteMessageEvent" }, viewQueries: [{ propertyName: "container", first: true, predicate: ["item"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2019-2024 RERO\n Copyright (C) 2019-2023 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div class=\"core:flex core:items-start\">\n <div class=\"core:grow\">\n <ng-container #item></ng-container>\n </div>\n <div class=\"core:flex core:flex-none core:gap-1 core:justify-end core:ml-2\">\n @if (adminMode.can) {\n <!-- use button -->\n @if (useStatus?.can && useStatus?.url) {\n <p-button\n [outlined]=\"true\"\n [ariaLabel]=\"'Use' | translate\"\n (onClick)=\"useRecord()\"\n [title]=\"'Use' | translate\"\n icon=\"fa fa-hand-o-right\"\n />\n }\n\n <!-- edit button -->\n @if (updateStatus?.can) {\n <p-button\n [outlined]=\"true\"\n [ariaLabel]=\"'Edit' | translate\"\n [routerLink]=\"updateStatus.routerLink || ['edit', record.id]\"\n icon=\"fa fa-pencil\"\n />\n }\n\n <!-- delete button -->\n @if (deleteStatus?.can || deleteStatus?.message) {\n <p-button\n icon=\"fa fa-trash\"\n severity=\"danger\"\n class=\"core:pointer-events-auto\"\n outlined\n [title]=\"'Delete'|translate\"\n (onClick)=\"deleteRecord(record.id, deleteStatus?.type)\"\n [pTooltip]=\"tooltipContent\"\n tooltipPosition=\"top\"\n [disabled]=\"!deleteStatus.can\"\n [tooltipDisabled]=\"deleteStatus.can\"\n />\n <ng-template #tooltipContent>\n <span [innerHTML]=\"deleteStatus.message\"></span>\n </ng-template>\n }\n }\n </div>\n</div>\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "directive", type: i2$8.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchResultComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-record-search-result', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2019-2024 RERO\n Copyright (C) 2019-2023 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<div class=\"core:flex core:items-start\">\n <div class=\"core:grow\">\n <ng-container #item></ng-container>\n </div>\n <div class=\"core:flex core:flex-none core:gap-1 core:justify-end core:ml-2\">\n @if (adminMode.can) {\n <!-- use button -->\n @if (useStatus?.can && useStatus?.url) {\n <p-button\n [outlined]=\"true\"\n [ariaLabel]=\"'Use' | translate\"\n (onClick)=\"useRecord()\"\n [title]=\"'Use' | translate\"\n icon=\"fa fa-hand-o-right\"\n />\n }\n\n <!-- edit button -->\n @if (updateStatus?.can) {\n <p-button\n [outlined]=\"true\"\n [ariaLabel]=\"'Edit' | translate\"\n [routerLink]=\"updateStatus.routerLink || ['edit', record.id]\"\n icon=\"fa fa-pencil\"\n />\n }\n\n <!-- delete button -->\n @if (deleteStatus?.can || deleteStatus?.message) {\n <p-button\n icon=\"fa fa-trash\"\n severity=\"danger\"\n class=\"core:pointer-events-auto\"\n outlined\n [title]=\"'Delete'|translate\"\n (onClick)=\"deleteRecord(record.id, deleteStatus?.type)\"\n [pTooltip]=\"tooltipContent\"\n tooltipPosition=\"top\"\n [disabled]=\"!deleteStatus.can\"\n [tooltipDisabled]=\"deleteStatus.can\"\n />\n <ng-template #tooltipContent>\n <span [innerHTML]=\"deleteStatus.message\"></span>\n </ng-template>\n }\n }\n </div>\n</div>\n" }]
}], propDecorators: { record: [{
type: Input
}], type: [{
type: Input
}], itemViewComponent: [{
type: Input
}], adminMode: [{
type: Input
}], canUpdate$: [{
type: Input
}], canDelete$: [{
type: Input
}], canUse$: [{
type: Input
}], aggregations: [{
type: Input
}], detailUrl$: [{
type: Input
}], deletedRecord: [{
type: Output
}], deleteMessageEvent: [{
type: Output
}], container: [{
type: ViewChild,
args: ['item', { read: ViewContainerRef }]
}] } });
/*
* RERO angular core
* Copyright (C) 2019-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* To implement your translation, please do it in your application
* by overloading this service.
* Example:
*
* import { BucketNameService as CoreBucketNameService } from '@rero/ng-core';
* import { BucketNameService } from './service/bucket-name.service';
*
* @NgModule({
* providers: [
* { provide: CoreBucketNameService, useClass: BucketNameService }
* ]
* });
*
*/
class BucketNameService {
constructor() {
this.translateService = inject(TranslateService);
}
/**
* Transform aggregation name
* @param aggregationKey - type of aggregation
* @param value - value of current aggregation
* @returns Observable of string
*/
transform(aggregationKey, value) {
let data = value;
// Legacy: for compatibility
switch (aggregationKey) {
case 'language':
data = `lang_${value}`;
break;
}
// End Legacy
return of(this.translateService.instant(data));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketNameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketNameService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketNameService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2019-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class BucketNamePipe {
constructor() {
this.bucketNameService = inject(BucketNameService);
}
/**
* Transform value if the type of aggregation is language
* @param value - aggregation value
* @param aggregationKey - aggregation type
* @returns Observable<string>
*/
transform(value, aggregationKey) {
return this.bucketNameService.transform(aggregationKey, value);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: BucketNamePipe, isStandalone: false, name: "bucketName" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketNamePipe, decorators: [{
type: Pipe,
args: [{
name: 'bucketName',
standalone: false
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class BucketsComponent {
constructor() {
this.recordSearchService = inject(RecordSearchService);
// COMPONENT ATTRIBUTES ============================================================
/** Buckets list for aggregation */
this.buckets = input.required();
/** Aggregation key */
this.aggregationKey = input.required();
/** Bucket size, if not null, reduce displayed items to this size. */
this.size = input();
/** More and less on aggregation content (facet) */
this.moreMode = true;
/** Current selected values for the aggregations */
this.aggregationsFilters = [];
/** Children of current bucket */
this.bucketChildren = {};
}
// GETTERS & SETTERS =================================================================
/** Returns selected filters for the aggregation key. */
get aggregationFilters() {
const aggregationFilters = this.aggregationsFilters.find((item) => item.key === this.aggregationKey());
return aggregationFilters === undefined
? []
: aggregationFilters.values;
}
/** Get bucket size. */
get bucketSize() {
const size = this.buckets().length;
return this.size() === null || this.moreMode === false
? size
: this.size();
}
/** Get the number of bucket items with non-zero doc_count. */
get bucketsLength() {
return this.buckets().reduce((acc, bucket) => (bucket.doc_count) ? acc + 1 : acc, 0);
}
/** Should display more or less link for a bucket ? */
get displayMoreAndLessLink() {
return (this.size() === null)
? false
: this.bucketsLength > this.size();
}
/**
* OnInit hook
* Subscribe to aggregations filters observable for getting the aggregations filters each time
* they will change.
*/
ngOnInit() {
this.aggregationsFiltersSubscription = this.recordSearchService
.aggregationsFilters
.subscribe((aggregationsFilters) => {
if (aggregationsFilters !== null) {
this.aggregationsFilters = aggregationsFilters;
}
});
}
/**
* OnChanges hook
* Called each time an input property is modified.
* If buckets are changed, refresh children buckets.
* @param changes - bucket changes
*/
ngOnChanges(changes) {
if (changes.buckets) {
for (const bucket of this.buckets()) {
this.bucketChildren[bucket.key] = this.getBucketChildren(bucket);
}
}
}
/**
* OnDestroy hook
* Unsubscribes from the observable of aggregations filters.
*/
ngOnDestroy() {
this.aggregationsFiltersSubscription.unsubscribe();
}
// COMPONENT F£UNCTIONS =====================================================
/**
* Check if a value is present in selected filters.
* @param value: filter value (could be string, number, ...)
* @return `true` if value is present in the aggregation filters, `null` otherwise.
*/
isSelected(value) {
return this.aggregationFilters.includes(value.toString()) ? true : null;
}
/**
* Do we need to display the children?
* @return `true` if we want to display the children, `false` otherwise.
*/
displayChildren(bucket) {
return (this.isSelected(bucket.key) ||
// not necessary but faster than hasChildrenFilter
bucket.indeterminate ||
// hasChildrenFilter is required to avoid blinks when a child is selected
this.recordSearchService.hasChildrenFilter(bucket));
}
/**
* Update selected filters by adding or removing the given value and push
* values to service.
* @param bucket - string: the selected bucket value (checked OR unchecked)
*/
updateFilter(bucket) {
const index = this.aggregationsFilters.findIndex((item) => item.key === this.aggregationKey());
if (index === -1) {
// No filters exist for the aggregation.
this.recordSearchService.updateAggregationFilter(this.aggregationKey(), [bucket.key], bucket);
}
else {
const aggFilter = this.aggregationsFilters[index];
if (!aggFilter.values.includes(bucket.key.toString())) {
// Bucket value is not yet selected, we add value to selected values.
this.aggregationsFilters[index].values.push(bucket.key.toString());
this.recordSearchService.updateAggregationFilter(this.aggregationKey(), this.aggregationsFilters[index].values, bucket);
}
else {
// Removes value from selected values and all children selected values.
this.recordSearchService.removeAggregationFilter(this.aggregationKey(), bucket);
}
}
}
/**
* Get children buckets
* @param bucket: parent bucket
* @return Bucket children list of given bucket
*/
getBucketChildren(bucket) {
const children = [];
for (const k of Object.keys(bucket).filter(key => typeof bucket[key] === 'object' && bucket[key].buckets)) {
children.push({
aggregationKey: k,
bucketSize: this.bucketSize,
key: k,
buckets: bucket[k].buckets,
});
}
return children;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: BucketsComponent, isStandalone: false, selector: "ng-core-record-search-aggregation-buckets", inputs: { buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: true, transformFunction: null }, aggregationKey: { classPropertyName: "aggregationKey", publicName: "aggregationKey", isSignal: true, isRequired: true, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, usesOnChanges: true, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (buckets()) {\n <ul class=\"core:list-none core:text-sm\">\n @for (bucket of buckets()|slice:0:bucketSize; track bucket.key; let index = $index) {\n <li>\n @if (bucket.doc_count) {\n <div class=\"core:flex core:gap-1 core:items-start core:text-link-secondary core:mb-1\">\n <p-checkbox\n inputId=\"{{ bucket.aggregationKey }}-{{ index }}\"\n binary\n [indeterminate]=\"bucket.indeterminate\"\n variant=\"filled\"\n [ngModel]=\"isSelected(bucket.key)\"\n (onChange)=\"updateFilter(bucket)\"\n />\n <label class=\"core:grow core:overflow-hidden\" for=\"{{ bucket.aggregationKey }}-{{ index }}\">\n @if (bucket.name) {\n {{ bucket.name }}\n } @else {\n {{ bucket.key | bucketName: aggregationKey() | async }}\n }\n </label>\n <div class=\"core:pl-2\">\n {{ bucket.doc_count }}\n </div>\n </div>\n }\n <div class=\"core:ml-6\">\n @if (displayChildren(bucket)) {\n @for (aggregation of bucketChildren[bucket.key]; track aggregation.key) {\n <ng-core-record-search-aggregation-buckets\n [buckets]=\"aggregation.buckets\"\n [size]=\"aggregation.bucketSize\"\n [aggregationKey]=\"aggregation.key\"\n ></ng-core-record-search-aggregation-buckets>\n }\n }\n </div>\n </li>\n }\n </ul>\n}\n@if (displayMoreAndLessLink) {\n <div class=\"core:flex core:justify-end\">\n <span class=\"text-link\" (click)=\"moreMode = !moreMode\">{{ (moreMode ? 'more\u2026' : 'less\u2026') | translate }}</span>\n </div>\n}\n", dependencies: [{ kind: "component", type: i2$5.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: BucketsComponent, selector: "ng-core-record-search-aggregation-buckets", inputs: ["buckets", "aggregationKey", "size"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.SlicePipe, name: "slice" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "pipe", type: BucketNamePipe, name: "bucketName" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: BucketsComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-record-search-aggregation-buckets', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (buckets()) {\n <ul class=\"core:list-none core:text-sm\">\n @for (bucket of buckets()|slice:0:bucketSize; track bucket.key; let index = $index) {\n <li>\n @if (bucket.doc_count) {\n <div class=\"core:flex core:gap-1 core:items-start core:text-link-secondary core:mb-1\">\n <p-checkbox\n inputId=\"{{ bucket.aggregationKey }}-{{ index }}\"\n binary\n [indeterminate]=\"bucket.indeterminate\"\n variant=\"filled\"\n [ngModel]=\"isSelected(bucket.key)\"\n (onChange)=\"updateFilter(bucket)\"\n />\n <label class=\"core:grow core:overflow-hidden\" for=\"{{ bucket.aggregationKey }}-{{ index }}\">\n @if (bucket.name) {\n {{ bucket.name }}\n } @else {\n {{ bucket.key | bucketName: aggregationKey() | async }}\n }\n </label>\n <div class=\"core:pl-2\">\n {{ bucket.doc_count }}\n </div>\n </div>\n }\n <div class=\"core:ml-6\">\n @if (displayChildren(bucket)) {\n @for (aggregation of bucketChildren[bucket.key]; track aggregation.key) {\n <ng-core-record-search-aggregation-buckets\n [buckets]=\"aggregation.buckets\"\n [size]=\"aggregation.bucketSize\"\n [aggregationKey]=\"aggregation.key\"\n ></ng-core-record-search-aggregation-buckets>\n }\n }\n </div>\n </li>\n }\n </ul>\n}\n@if (displayMoreAndLessLink) {\n <div class=\"core:flex core:justify-end\">\n <span class=\"text-link\" (click)=\"moreMode = !moreMode\">{{ (moreMode ? 'more\u2026' : 'less\u2026') | translate }}</span>\n </div>\n}\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class AggregationSliderComponent {
constructor() {
this.recordSearchService = inject(RecordSearchService);
// COMPONENT ATTRIBUTES =====================================================
/** The aggregation key. */
this.key = input();
/** Buckets list */
this.buckets = input();
/** The minimum value */
this.min = input(1);
/** The maximum value */
this.max = input(1000);
/** gap between each value. */
this.step = input(1);
/** Range to search */
this.range = null;
/** True if route have aggregation query param. */
this.hasQueryParam = false;
}
/**
* OnInit hook
* Subscribe to route changes for getting aggregation query parameter.
*/
ngOnInit() {
this.range = [this.min(), this.max()];
this.searchServiceSubscription = this.recordSearchService
.aggregationsFilters
.subscribe((filters) => {
if (!filters) {
return;
}
let filter = filters.find((element) => element.key === this.key());
if (filter) {
filter = filter.values[0].split('--').map((item) => +item);
this.hasQueryParam = true;
this.range = filter;
}
else {
this.range = [this.min(), this.max()];
}
});
}
/**
* OnDestroy hook
* Unsubscribes from search service.
*/
ngOnDestroy() {
this.searchServiceSubscription.unsubscribe();
}
// COMPONENT FUNCTIONS ======================================================
/** Update aggregation filter. */
updateFilter() {
if (!this.range[0] || this.range[0] < this.min()) {
this.range[0] = this.min();
}
if (!this.range[1] || this.range[1] > this.max()) {
this.range[1] = this.max();
}
this.recordSearchService.updateAggregationFilter(this.key(), [
`${this.range[0]}--${this.range[1]}`,
]);
}
/** Clear aggregation filter. */
clearFilter() {
this.hasQueryParam = false;
this.recordSearchService.updateAggregationFilter(this.key(), []);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AggregationSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: AggregationSliderComponent, isStandalone: false, selector: "ng-core-aggregation-slider", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: false, transformFunction: null }, buckets: { classPropertyName: "buckets", publicName: "buckets", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-inputGroup>\n <p-inputNumber\n class=\"core:mr-1\"\n inputId=\"integeronly\"\n [useGrouping]=\"false\"\n [(ngModel)]=\"range[0]\"\n [min]=\"range[0]\"\n [max]=\"range[1]\" />\n <p-inputNumber\n class=\"core:ml-1\"\n inputId=\"integeronly\"\n [useGrouping]=\"false\"\n [(ngModel)]=\"range[1]\"\n [min]=\"range[0]\"\n [max]=\"range[1]\" />\n</p-inputGroup>\n<div class=\"core:flex core:justify-end core:mt-2 core:gap-2\">\n @if (hasQueryParam) {\n <p-button size=\"small\" [label]=\"'Clear' | translate\" [outlined]=\"true\" (onClick)=\"$event.preventDefault(); clearFilter()\" />\n }\n <p-button size=\"small\" [label]=\"'Apply' | translate\" [outlined]=\"true\" (onClick)=\"$event.preventDefault(); updateFilter()\" />\n</div>\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.InputGroup, selector: "p-inputgroup, p-inputGroup, p-input-group", inputs: ["style", "styleClass"] }, { kind: "component", type: i3$6.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "style", "placeholder", "size", "maxlength", "tabindex", "title", "ariaLabelledBy", "ariaDescribedBy", "ariaLabel", "ariaRequired", "name", "required", "autocomplete", "min", "max", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "step", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "variant", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus", "disabled", "fluid"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AggregationSliderComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-aggregation-slider', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-inputGroup>\n <p-inputNumber\n class=\"core:mr-1\"\n inputId=\"integeronly\"\n [useGrouping]=\"false\"\n [(ngModel)]=\"range[0]\"\n [min]=\"range[0]\"\n [max]=\"range[1]\" />\n <p-inputNumber\n class=\"core:ml-1\"\n inputId=\"integeronly\"\n [useGrouping]=\"false\"\n [(ngModel)]=\"range[1]\"\n [min]=\"range[0]\"\n [max]=\"range[1]\" />\n</p-inputGroup>\n<div class=\"core:flex core:justify-end core:mt-2 core:gap-2\">\n @if (hasQueryParam) {\n <p-button size=\"small\" [label]=\"'Clear' | translate\" [outlined]=\"true\" (onClick)=\"$event.preventDefault(); clearFilter()\" />\n }\n <p-button size=\"small\" [label]=\"'Apply' | translate\" [outlined]=\"true\" (onClick)=\"$event.preventDefault(); updateFilter()\" />\n</div>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2021-2025 RERO
* Copyright (C) 2021 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class AggregationDateRangeComponent {
constructor() {
this.recordSearchService = inject(RecordSearchService);
this.translateService = inject(TranslateService);
// COMPONENTS ATTRIBUTES ====================================================
// Aggregation key
this.key = input.required();
// Components configuration
this.config = input.required();
// Date minimum
this.minDate = null;
// Date maximum
this.maxDate = null;
/** True if route have aggregation query param. */
this.hasQueryParam = false;
this.buttonConfig = [];
}
/** OnInit hook */
ngOnInit() {
// Define min/max values if not exists
if (this.config().min) {
this.minDate = new Date(this.config().min);
}
else {
this.minDate = new Date(1900, 0, 0);
}
if (this.config().max) {
this.maxDate = new Date(this.config().max);
}
else {
this.maxDate = new Date();
}
this.dateRangeModel = [this.minDate, this.maxDate];
this.searchServiceSubscription = this.recordSearchService.aggregationsFilters.subscribe((filters) => {
if (!filters) {
return;
}
const filter = filters.find((element) => element.key === this.key());
this.hasQueryParam = filter !== undefined;
this.dateRangeModel = (filter)
? filter.values[0].split('--').map((item) => new Date(+item)) // split filter data and transform each part into Date
: [this.minDate, this.maxDate];
});
const today = new Date();
this.buttonConfig = [
{
value: [new Date(new Date().setDate(today.getDate() - 31)), today],
label: this.translateService.instant('Last month')
}, {
value: [new Date(new Date().setDate(today.getDate() - 366)), today],
label: this.translateService.instant('Last year')
}, {
value: [new Date(today.getFullYear(), today.getMonth(), 1), new Date(today.getFullYear(), today.getMonth() + 1, 0)],
label: this.translateService.instant('This month')
},
{
value: [new Date(today.getFullYear(), 0, 1), new Date(today.getFullYear() + 1, 0, 1)],
label: this.translateService.instant('This year')
}
];
}
setRange(value) {
this.dateRangeModel = value;
this.updateFilter();
}
/** OnDestroy hook */
ngOnDestroy() {
this.searchServiceSubscription.unsubscribe();
}
// COMPONENTS FUNCTIONS =====================================================
/** Update aggregation filter. */
updateFilter() {
if (this.dateRangeModel && this.dateRangeModel[0] && this.dateRangeModel[1]) {
this.recordSearchService.updateAggregationFilter(this.key(), [
`${this.dateRangeModel[0].getTime()}--${this.dateRangeModel[1].getTime()}`
]);
}
}
/** Clear aggregation filter. */
clearFilter() {
this.recordSearchService.updateAggregationFilter(this.key(), []);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AggregationDateRangeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: AggregationDateRangeComponent, isStandalone: false, selector: "ng-core-aggregation-date-range", inputs: { key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2021-2025 RERO\n Copyright (C) 2021 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-datepicker\n styleClass=\"core:w-full\"\n inputStyleClass=\"core:text-center\"\n [(ngModel)]=\"dateRangeModel\"\n selectionMode=\"range\"\n showIcon\n>\n <ng-template #footer>\n <p-divider/>\n <div class=\"core:grid core:grid-cols-12 core:gap-4\">\n @for (cfg of buttonConfig; track cfg.value) {\n <div class=\"core:col-span-6 core:flex core:justify-center\">\n <p-button (onClick)=\"setRange(cfg.value)\" styleClass=\"core:w-full\" class=\"core:grow core:p-1\" outlined size=\"small\" [label]=\"cfg.label\" />\n </div>\n }\n </div>\n </ng-template>\n</p-datepicker>\n<div class=\"core:flex core:justify-end core:mt-2 core:gap-2\">\n @if (hasQueryParam) {\n <p-button size=\"small\" [label]=\"'Clear' | translate\" [outlined]=\"true\" (onClick)=\"clearFilter()\" />\n }\n <p-button size=\"small\" [label]=\"'Apply' | translate\" [outlined]=\"true\" (onClick)=\"updateFilter()\" />\n</div>\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$4.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "fluid", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "size", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "component", type: i2$3.Divider, selector: "p-divider", inputs: ["style", "styleClass", "layout", "type", "align"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AggregationDateRangeComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-aggregation-date-range', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2021-2025 RERO\n Copyright (C) 2021 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-datepicker\n styleClass=\"core:w-full\"\n inputStyleClass=\"core:text-center\"\n [(ngModel)]=\"dateRangeModel\"\n selectionMode=\"range\"\n showIcon\n>\n <ng-template #footer>\n <p-divider/>\n <div class=\"core:grid core:grid-cols-12 core:gap-4\">\n @for (cfg of buttonConfig; track cfg.value) {\n <div class=\"core:col-span-6 core:flex core:justify-center\">\n <p-button (onClick)=\"setRange(cfg.value)\" styleClass=\"core:w-full\" class=\"core:grow core:p-1\" outlined size=\"small\" [label]=\"cfg.label\" />\n </div>\n }\n </div>\n </ng-template>\n</p-datepicker>\n<div class=\"core:flex core:justify-end core:mt-2 core:gap-2\">\n @if (hasQueryParam) {\n <p-button size=\"small\" [label]=\"'Clear' | translate\" [outlined]=\"true\" (onClick)=\"clearFilter()\" />\n }\n <p-button size=\"small\" [label]=\"'Apply' | translate\" [outlined]=\"true\" (onClick)=\"updateFilter()\" />\n</div>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordSearchAggregationComponent {
constructor() {
// COMPONENT ATTRIBUTES =====================================================
/** Aggregation data */
this.aggregation = input();
/** Current selected values */
this.aggregationsFilters = input.required();
/** Output event when aggregation is clicked. */
this.aggregationClicked = output();
}
// GETTER & SETTER ==========================================================
/**
* Returns aggregations filters corresponding to the aggregation key.
* @return List of aggregation filters
*/
get aggregationFilters() {
const aggregationFilters = this.aggregationsFilters().find((item) => item.key === this.aggregation().key);
return aggregationFilters === undefined ? [] : aggregationFilters.values;
}
/** Is the aggregation content should be displayed. */
get isAggregationDisplayed() {
return this.aggregation().expanded || this.aggregationFilters.length > 0;
}
// PUBLIC COMPONENTS ========================================================
/**
* Method called when the title of an aggregation is clicked.
*/
toggleVisibility() {
// if filters are selected, we do nothing.
if (this.aggregationFilters.length > 0) {
return;
}
// Toggle aggregation expanded attribute
this.aggregation().expanded = !this.aggregation().expanded;
// Emit the value to parent.
this.aggregationClicked.emit({
key: this.aggregation().key,
expanded: this.aggregation().expanded,
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchAggregationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: RecordSearchAggregationComponent, isStandalone: false, selector: "ng-core-record-search-aggregation", inputs: { aggregation: { classPropertyName: "aggregation", publicName: "aggregation", isSignal: true, isRequired: false, transformFunction: null }, aggregationsFilters: { classPropertyName: "aggregationsFilters", publicName: "aggregationsFilters", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { aggregationClicked: "aggregationClicked" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<!-- Aggregation with no bucket, containing only the count. -->\n@if (aggregation().doc_count) {\n <div class=\"core:mb-4\">\n <ng-core-record-search-aggregation-buckets\n [buckets]=\"[{key: '1', doc_count: aggregation().doc_count, name: aggregation().key | translate }]\"\n [aggregationKey]=\"aggregation().key\"\n [size]=\"aggregation().bucketSize\" />\n </div>\n} @else {\n <p-fieldset\n [legend]=\"(aggregation().name || aggregation().key) | translate | ucfirst\"\n [toggleable]=\"true\"\n [collapsed]=\"!isAggregationDisplayed\"\n (collapsedChange)=\"toggleVisibility()\"\n >\n <div class=\"core:p-2\">\n @switch (aggregation().type) {\n @case ('range') {\n <ng-core-aggregation-slider\n [key]=\"aggregation().key\"\n [min]=\"aggregation().config.min || 1\"\n [max]=\"aggregation().config.max || 100\"\n [step]=\"aggregation().config.step || 1\"\n [buckets]=\"aggregation().value.buckets\" />\n }\n @case ('date-range') {\n <ng-core-aggregation-date-range\n [key]=\"aggregation().key\"\n [config]=\"aggregation().config\" />\n }\n @default {\n @if (aggregation().value.buckets.length > 0) {\n <ng-core-record-search-aggregation-buckets\n [buckets]=\"aggregation().value.buckets\"\n [aggregationKey]=\"aggregation().key\"\n [size]=\"aggregation().bucketSize\" />\n } @else {\n @if (aggregation().loaded) {\n <span class=\"core:text-sm\" translate>No result</span>\n }\n }\n }\n }\n</div>\n</p-fieldset>\n}\n", dependencies: [{ kind: "component", type: i1$7.Fieldset, selector: "p-fieldset", inputs: ["legend", "toggleable", "collapsed", "style", "styleClass", "transitionOptions"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "directive", type: i5.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: BucketsComponent, selector: "ng-core-record-search-aggregation-buckets", inputs: ["buckets", "aggregationKey", "size"] }, { kind: "component", type: AggregationSliderComponent, selector: "ng-core-aggregation-slider", inputs: ["key", "buckets", "min", "max", "step"] }, { kind: "component", type: AggregationDateRangeComponent, selector: "ng-core-aggregation-date-range", inputs: ["key", "config"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "pipe", type: UpperCaseFirstPipe, name: "ucfirst" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchAggregationComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-record-search-aggregation', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<!-- Aggregation with no bucket, containing only the count. -->\n@if (aggregation().doc_count) {\n <div class=\"core:mb-4\">\n <ng-core-record-search-aggregation-buckets\n [buckets]=\"[{key: '1', doc_count: aggregation().doc_count, name: aggregation().key | translate }]\"\n [aggregationKey]=\"aggregation().key\"\n [size]=\"aggregation().bucketSize\" />\n </div>\n} @else {\n <p-fieldset\n [legend]=\"(aggregation().name || aggregation().key) | translate | ucfirst\"\n [toggleable]=\"true\"\n [collapsed]=\"!isAggregationDisplayed\"\n (collapsedChange)=\"toggleVisibility()\"\n >\n <div class=\"core:p-2\">\n @switch (aggregation().type) {\n @case ('range') {\n <ng-core-aggregation-slider\n [key]=\"aggregation().key\"\n [min]=\"aggregation().config.min || 1\"\n [max]=\"aggregation().config.max || 100\"\n [step]=\"aggregation().config.step || 1\"\n [buckets]=\"aggregation().value.buckets\" />\n }\n @case ('date-range') {\n <ng-core-aggregation-date-range\n [key]=\"aggregation().key\"\n [config]=\"aggregation().config\" />\n }\n @default {\n @if (aggregation().value.buckets.length > 0) {\n <ng-core-record-search-aggregation-buckets\n [buckets]=\"aggregation().value.buckets\"\n [aggregationKey]=\"aggregation().key\"\n [size]=\"aggregation().bucketSize\" />\n } @else {\n @if (aggregation().loaded) {\n <span class=\"core:text-sm\" translate>No result</span>\n }\n }\n }\n }\n</div>\n</p-fieldset>\n}\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2022-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class ListFiltersComponent {
constructor() {
this.translateService = inject(TranslateService);
/**
* All aggregations
*/
this.aggregations = input([]);
// Selected aggregations filters
this.aggregationsFilters = input([]);
// Search filters
this.searchFilters = input([]);
/** Remove filter event */
this.remove = output();
// Filters to hide
this.filtersToHide = ['simple'];
// Filters selected
this.filters = computed(() => this.getActiveFilters());
}
/**
* Returns promised filters
* @returns the array of IFilter
*/
getActiveFilters() {
const filterSet = [];
this.searchFilters().forEach((filter) => {
if (!filter.filters) {
filterSet.push(filter.filter);
}
else {
filter.filters.forEach((fSection) => {
filterSet.push(fSection.filter);
});
}
});
this.filtersToHide = [...new Set([...this.filtersToHide, ...filterSet])];
let filters = [];
this.aggregationsFilters().map((filter) => {
if (!this.filtersToHide.includes(filter.key)) {
filter.values.map((value) => {
if (value.includes('--')) {
const regex = /\d{13,}--\d{13,}/;
if (regex.test(value)) {
const [min, max] = value.split('--');
const dateMin = new Intl.DateTimeFormat(this.translateService.currentLang).format(+min);
const dateMax = new Intl.DateTimeFormat(this.translateService.currentLang).format(+max);
filters.push({ key: value, aggregationKey: filter.key, name: `${dateMin} - ${dateMax}` });
}
else {
filters.push({ key: value, aggregationKey: filter.key, name: value.replace('--', ' - ') });
}
}
else {
filters.push({ key: value, aggregationKey: filter.key });
}
});
}
});
if (this.aggregations()) {
this.aggregations().map((item) => {
this.getFilterNames(item.value.buckets, filters);
});
}
return filters;
}
/**
* Get displayed name of bucket
* and fill in filters list.
*
* @param buckets - Bucket to get the name from.
*/
getFilterNames(buckets, filters) {
if (!buckets || buckets.length === 0) {
return;
}
buckets.map((bucket) => {
for (const k in bucket) {
if (bucket[k].buckets) {
this.getFilterNames(bucket[k].buckets, filters);
}
}
if (bucket.name) {
const index = filters.findIndex((filter) => filter.key === bucket.key && filter.aggregationKey === bucket.aggregationKey);
if (index > -1) {
filters[index].name = bucket.name;
filters[index] = { ...filters[index] };
}
}
});
}
/**
* Remove filter.
* @param filter - the filter to remove
*/
removeFilter(filter) {
this.remove.emit(filter);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ListFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ListFiltersComponent, isStandalone: false, selector: "ng-core-list-filters", inputs: { aggregations: { classPropertyName: "aggregations", publicName: "aggregations", isSignal: true, isRequired: false, transformFunction: null }, aggregationsFilters: { classPropertyName: "aggregationsFilters", publicName: "aggregationsFilters", isSignal: true, isRequired: false, transformFunction: null }, searchFilters: { classPropertyName: "searchFilters", publicName: "searchFilters", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { remove: "remove" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2022-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (filters()?.length > 0) {\n <div class=\"core:flex core:flex-wrap core:mb-2\">\n @for (filter of filters(); track filter.key) {\n <p-button\n class=\"core:mr-2 core:mb-2\"\n [label]=\"filter.name ? filter.name : (filter.key | bucketName : filter.aggregationKey | async)\"\n icon=\"fa fa-times\"\n [outlined]=\"true\"\n severity=\"secondary\"\n (onClick)=\"removeFilter(filter)\"\n />\n }\n </div>\n }\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: BucketNamePipe, name: "bucketName" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ListFiltersComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-list-filters', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2022-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (filters()?.length > 0) {\n <div class=\"core:flex core:flex-wrap core:mb-2\">\n @for (filter of filters(); track filter.key) {\n <p-button\n class=\"core:mr-2 core:mb-2\"\n [label]=\"filter.name ? filter.name : (filter.key | bucketName : filter.aggregationKey | async)\"\n icon=\"fa fa-times\"\n [outlined]=\"true\"\n severity=\"secondary\"\n (onClick)=\"removeFilter(filter)\"\n />\n }\n </div>\n }\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class PaginatorComponent {
constructor() {
/** Input */
this.alwaysShow = input(true);
this.currentPage = input(1);
this.pageLinkSize = input(5);
this.rows = input(10);
this.rowsPerPageOptions = input();
this.showCurrentPageReport = input(false);
this.showFirstLastIcon = input(false);
this.totalRecords = input(0);
/** Output */
this.rowPageChange = output();
/** Position for the current page */
this.first = computed(() => {
this.eventData.page = (this.currentPage() - 1);
this.eventData.rows = this.rows();
return this.eventData.page * this.eventData.rows;
});
/** Paginator Event */
this.eventData = {
page: 0,
rows: 10
};
}
/** Event on change page */
onPageChange(event) {
this.eventData.rows = event.rows;
this.eventData.page = event.page;
this.rowPageChange.emit(this.eventData);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PaginatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: PaginatorComponent, isStandalone: false, selector: "ng-core-paginator", inputs: { alwaysShow: { classPropertyName: "alwaysShow", publicName: "alwaysShow", isSignal: true, isRequired: false, transformFunction: null }, currentPage: { classPropertyName: "currentPage", publicName: "currentPage", isSignal: true, isRequired: false, transformFunction: null }, pageLinkSize: { classPropertyName: "pageLinkSize", publicName: "pageLinkSize", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, rowsPerPageOptions: { classPropertyName: "rowsPerPageOptions", publicName: "rowsPerPageOptions", isSignal: true, isRequired: false, transformFunction: null }, showCurrentPageReport: { classPropertyName: "showCurrentPageReport", publicName: "showCurrentPageReport", isSignal: true, isRequired: false, transformFunction: null }, showFirstLastIcon: { classPropertyName: "showFirstLastIcon", publicName: "showFirstLastIcon", isSignal: true, isRequired: false, transformFunction: null }, totalRecords: { classPropertyName: "totalRecords", publicName: "totalRecords", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rowPageChange: "rowPageChange" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-paginator\n (onPageChange)=\"onPageChange($event)\"\n [alwaysShow]=\"alwaysShow()\"\n [first]=\"first()\"\n [pageLinkSize]=\"pageLinkSize()\"\n [rows]=\"rows()\"\n [showCurrentPageReport]=\"showCurrentPageReport()\"\n [showFirstLastIcon]=\"showFirstLastIcon()\"\n [totalRecords]=\"totalRecords()\"\n [rowsPerPageOptions]=\"rowsPerPageOptions()\" />\n", dependencies: [{ kind: "component", type: i1$8.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PaginatorComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-paginator', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-paginator\n (onPageChange)=\"onPageChange($event)\"\n [alwaysShow]=\"alwaysShow()\"\n [first]=\"first()\"\n [pageLinkSize]=\"pageLinkSize()\"\n [rows]=\"rows()\"\n [showCurrentPageReport]=\"showCurrentPageReport()\"\n [showFirstLastIcon]=\"showFirstLastIcon()\"\n [totalRecords]=\"totalRecords()\"\n [rowsPerPageOptions]=\"rowsPerPageOptions()\" />\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2022-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class MenuSortComponent {
constructor() {
this.translateService = inject(TranslateService);
this.activatedRoute = inject(ActivatedRoute);
/** Input */
this.config = input.required();
this.selectedValue = input();
this.language = toSignal(this.translateService.onLangChange);
this.selectedOption = computed(() => this.config().find((conf) => conf.value === this.selectedValue()));
this.options = computed(() => { this.language(); return this.sortOptions(); });
/** Change event */
this.onChange = output();
}
/** Return the sort options from config. */
sortOptions() {
const translations = [];
this.config().map((option) => {
const newOption = { ...option };
newOption.label = this.translateService.instant(option.label);
newOption.command = (event) => this.onChange.emit(event.item.value);
translations.push(newOption);
});
return translations.sort((a, b) => a.label.localeCompare(b.label));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MenuSortComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: MenuSortComponent, isStandalone: false, selector: "ng-core-menu-sort", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, selectedValue: { classPropertyName: "selectedValue", publicName: "selectedValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChange: "onChange" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2019-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (options().length > 0) {\n<p-menu #menu [model]=\"options()\" [popup]=\"true\" />\n<p-button severity=\"secondary\" outlined (onClick)=\"menu.toggle($event)\">\n <div class=\"core:flex core:gap-4 core:items-center\">\n @if (selectedOption()?.icon) {\n <i [ngClass]=\"selectedOption().icon\"></i>\n } @else if (selectedOption()) {\n {{'Sort by' | translate }} :\n }\n @if (selectedOption()?.label) {\n {{ selectedOption()?.label | translate }}\n } @else {\n {{'Sort by' | translate }} \u2026\n }\n <i class=\"fa fa-caret-down\"></i>\n</div>\n</p-button>\n}\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$7.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "appendTo", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MenuSortComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-menu-sort', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2019-2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (options().length > 0) {\n<p-menu #menu [model]=\"options()\" [popup]=\"true\" />\n<p-button severity=\"secondary\" outlined (onClick)=\"menu.toggle($event)\">\n <div class=\"core:flex core:gap-4 core:items-center\">\n @if (selectedOption()?.icon) {\n <i [ngClass]=\"selectedOption().icon\"></i>\n } @else if (selectedOption()) {\n {{'Sort by' | translate }} :\n }\n @if (selectedOption()?.label) {\n {{ selectedOption()?.label | translate }}\n } @else {\n {{'Sort by' | translate }} \u2026\n }\n <i class=\"fa fa-caret-down\"></i>\n</div>\n</p-button>\n}\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class SearchFiltersComponent {
constructor() {
this.activatedRoute = inject(ActivatedRoute);
// Array of filters
this.searchFilters = input([]);
// Resources configuration
this.config = input.required();
// query string
this.query = input.required();
// Class for section
this.styleClassSection = input();
// Filter event
this.onChange = output();
}
/**
* Check if a filter section
* @param searchFilterSection - SearchFilterSection
* @returns True if the filter is the section
*/
showFilterSection(searchFilterSection) {
return searchFilterSection.filters.some((filter) => this.config().allowEmptySearch
? true
: (this.query() && filter.showIfQuery === true) || !filter?.showIfQuery);
}
/**
* Check if a simple filter
* @param searchFilter - SearchFilter
* @returns True if the filter is simple.
*/
showFilter(searchFilter) {
if (this.config().allowEmptySearch) {
return true;
}
if (!this.query()) {
return !(searchFilter.showIfQuery === true);
}
else {
return (searchFilter.showIfQuery === true || !searchFilter?.showIfQuery);
}
}
/**
* Activates the filter according to the route parameters.
* @param filter - SearchFilter
* @returns True, if the parameter is present nd contains a value.
*/
isChecked(filter) {
if (this.activatedRoute.snapshot.queryParamMap.has(filter.filter)) {
const value = this.activatedRoute.snapshot.queryParamMap.get(filter.filter);
if (!value) {
return false;
}
if (filter.filter === 'simple') {
return !Boolean(JSON.parse(value));
}
else {
return Boolean(JSON.parse(value));
}
}
return false;
}
/**
* Event on switch
* @param event - ToggleSwitchChangeEvent primeng
* @param filter the search filter
*/
change(event, filter) {
this.onChange.emit({ filterKey: filter.filter, checked: event.checked });
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SearchFiltersComponent, isStandalone: false, selector: "ng-core-search-filters", inputs: { searchFilters: { classPropertyName: "searchFilters", publicName: "searchFilters", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: true, transformFunction: null }, styleClassSection: { classPropertyName: "styleClassSection", publicName: "styleClassSection", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChange: "onChange" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2024-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (searchFilters().length > 0) {\n <div class=\"core:mb-2\">\n @for (searchFilter of searchFilters(); track searchFilter.filter) {\n @if (searchFilter.filters) {\n @if (showFilterSection(searchFilter)) {\n <div [ngClass]=\"styleClassSection()\">{{ searchFilter.label | translate }}</div>\n @for(searchFilterSection of searchFilter.filters; track searchFilterSection.filter) {\n @if (showFilter(searchFilterSection)) {\n <ng-container [ngTemplateOutlet]=\"searchFilterTemplate\" [ngTemplateOutletContext]=\"{filter: searchFilterSection}\"/>\n }\n }\n }\n } @else {\n @if (showFilter(searchFilter)) {\n <ng-container [ngTemplateOutlet]=\"searchFilterTemplate\" [ngTemplateOutletContext]=\"{filter: searchFilter}\"/>\n }\n }\n }\n </div>\n}\n\n<ng-template #searchFilterTemplate let-filter=\"filter\">\n <div class=\"core:flex\">\n <div class=\"core:flex core:items-center core:mr-2\">\n <p-toggleswitch\n [ngModel]=\"isChecked(filter)\"\n inputId=\"customSwitch-{{ filter.filter }}\"\n [ariaLabel]=\"filter.label | translate\"\n (onChange)=\"change($event, filter)\"\n />\n </div>\n <div class=\"core:flex core:items-center\">\n <label for=\"customSwitch-{{ filter.filter }}\">{{ filter.label | translate }}</label>\n @if (filter.url) {\n @if (filter.url.external) {\n <a class=\"core:ml-1\" [title]=\"filter.url.title\" [href]=\"filter.url.link\" [target]=\"filter.url.target\">\n <i class=\"fa fa-info-circle \" aria-hidden=\"true\"></i>\n </a>\n } @else {\n <a class=\"core:ml-1\" [title]=\"filter.url.title\" [routerLink]=\"filter.url.routerLink\">\n <i class=\"fa fa-info-circle \" aria-hidden=\"true\"></i>\n </a>\n }\n }\n </div>\n </div>\n</ng-template>\n", dependencies: [{ kind: "component", type: i1$4.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["style", "styleClass", "tabindex", "inputId", "name", "disabled", "readonly", "trueValue", "falseValue", "ariaLabel", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$8.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchFiltersComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-search-filters', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2024-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (searchFilters().length > 0) {\n <div class=\"core:mb-2\">\n @for (searchFilter of searchFilters(); track searchFilter.filter) {\n @if (searchFilter.filters) {\n @if (showFilterSection(searchFilter)) {\n <div [ngClass]=\"styleClassSection()\">{{ searchFilter.label | translate }}</div>\n @for(searchFilterSection of searchFilter.filters; track searchFilterSection.filter) {\n @if (showFilter(searchFilterSection)) {\n <ng-container [ngTemplateOutlet]=\"searchFilterTemplate\" [ngTemplateOutletContext]=\"{filter: searchFilterSection}\"/>\n }\n }\n }\n } @else {\n @if (showFilter(searchFilter)) {\n <ng-container [ngTemplateOutlet]=\"searchFilterTemplate\" [ngTemplateOutletContext]=\"{filter: searchFilter}\"/>\n }\n }\n }\n </div>\n}\n\n<ng-template #searchFilterTemplate let-filter=\"filter\">\n <div class=\"core:flex\">\n <div class=\"core:flex core:items-center core:mr-2\">\n <p-toggleswitch\n [ngModel]=\"isChecked(filter)\"\n inputId=\"customSwitch-{{ filter.filter }}\"\n [ariaLabel]=\"filter.label | translate\"\n (onChange)=\"change($event, filter)\"\n />\n </div>\n <div class=\"core:flex core:items-center\">\n <label for=\"customSwitch-{{ filter.filter }}\">{{ filter.label | translate }}</label>\n @if (filter.url) {\n @if (filter.url.external) {\n <a class=\"core:ml-1\" [title]=\"filter.url.title\" [href]=\"filter.url.link\" [target]=\"filter.url.target\">\n <i class=\"fa fa-info-circle \" aria-hidden=\"true\"></i>\n </a>\n } @else {\n <a class=\"core:ml-1\" [title]=\"filter.url.title\" [routerLink]=\"filter.url.routerLink\">\n <i class=\"fa fa-info-circle \" aria-hidden=\"true\"></i>\n </a>\n }\n }\n </div>\n </div>\n</ng-template>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;
class SearchTabsComponent {
constructor() {
this.activatedRoute = inject(ActivatedRoute);
// Resources configuration
this.types = input.required();
// Tab change event
this.onChangeType = output();
/** Tab index */
this.currentType = model(undefined);
}
/** OnInit hook */
ngOnInit() {
this.typesInTabs = this.types().filter((item) => item.hideInTabs !== true);
this.currentType.set(this.activatedRoute.snapshot.params.type);
this.currentTypeSubscription = this.currentType.subscribe(typeName => this.onChangeType.emit(typeName));
}
ngOnDestroy() {
this.currentTypeSubscription.unsubscribe();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SearchTabsComponent, isStandalone: false, selector: "ng-core-search-tabs", inputs: { types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: true, transformFunction: null }, currentType: { classPropertyName: "currentType", publicName: "currentType", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChangeType: "onChangeType", currentType: "currentTypeChange" }, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2024-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (typesInTabs.length > 1) {\n <p-tabs [(value)]=\"currentType\">\n <p-tablist>\n @for (tab of typesInTabs; track tab.key) {\n <p-tab [value]=\"tab.key\">{{ tab.label | translate }}</p-tab>\n }\n </p-tablist>\n </p-tabs>\n}\n", dependencies: [{ kind: "component", type: i1$9.Tabs, selector: "p-tabs", inputs: ["value", "scrollable", "lazy", "selectOnFocus", "showNavigators", "tabindex"], outputs: ["valueChange"] }, { kind: "component", type: i1$9.TabList, selector: "p-tablist" }, { kind: "component", type: i1$9.Tab, selector: "p-tab", inputs: ["value", "disabled"], outputs: ["valueChange"] }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchTabsComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-search-tabs', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2024-2025 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (typesInTabs.length > 1) {\n <p-tabs [(value)]=\"currentType\">\n <p-tablist>\n @for (tab of typesInTabs; track tab.key) {\n <p-tab [value]=\"tab.key\">{{ tab.label | translate }}</p-tab>\n }\n </p-tablist>\n </p-tabs>\n}\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2022-2025 RERO
* Copyright (C) 2022 UCLouvain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordSearchComponent {
constructor() {
this.recordService = inject(RecordService);
this.recordUiService = inject(RecordUiService);
this.recordSearchService = inject(RecordSearchService);
this.translateService = inject(TranslateService);
this.spinner = inject(NgxSpinnerService);
this.apiService = inject(ApiService);
this.activatedRoute = inject(ActivatedRoute);
// COMPONENT ATTRIBUTES =====================================================
/** Current selected resource type */
this.currentType = null;
/** Search query */
this.q = '';
/** Define the current record's page */
this.page = 1;
/** Define the number of records per page */
this.size = 10;
/** Defined the sort order */
this.sort = null;
/** If admin mode is disabled, no action can be performed on a record (as add, update or remove) */
this.adminMode = { can: true, message: '' };
/** Custom URL to notice detail */
this.detailUrl = null;
/** Resources types available */
this.types = [{ key: 'documents', label: 'Documents' }];
/** Output current state when parameters change. */
this.parametersChanged = new EventEmitter();
/** Output current result of records by type. */
this.recordsSearched = new EventEmitter();
/** Current aggregations filters applied */
this.aggregationsFilters = [];
/** Contain result row data */
this.hits = [];
/** Aggregation keys to always hide (defined into the config) */
this.aggregationsToHide = [];
/** Check if record can be added */
this.addStatus = { can: true, message: '' };
/** List of fields on which we can do a specific search. */
this.searchFields = [];
/** List of fields on which we can filter. */
this.searchFilters = [];
/** If we need to show the empty search message info. */
this.showEmptySearchMessage = false;
/** JSON dumping of last search parameters (used for checking if we have to do a search or not).*/
this._searchParameters = new BehaviorSubject(null);
/** Define if search input have to be displayed or not. */
this._showSearchInput = true;
/** Define if title have to be displayed or not. */
this._showLabel = true;
/** Subscriptions to observables. */
this._subscriptions = new Subscription();
/** Store configuration for type. */
this.config = null;
this.availableTypes = [];
this.loaded = false;
}
// GETTER & SETTER ==========================================================
/** Check if pagination have to be displayed or not. */
get showPagination() {
return this.total > this.size;
}
/** Does it require to activate the first and last button on pagination. */
get paginationBoundaryLinks() {
const paginationConfig = this._getResourceConfig('pagination', {});
return 'boundaryLinks' in paginationConfig ? paginationConfig.boundaryLinks : false;
}
/** Get the number of pages showed on pagination. */
get paginationMaxSize() {
const paginationConfig = this._getResourceConfig('pagination', {});
return 'maxSize' in paginationConfig ? paginationConfig.maxSize : 5;
}
/** Request result record hits. */
get records() {
return this.hits && this.hits.hits ? this.hits.hits : [];
}
/** Total records number corresponding to the request. */
get total() {
return this.hits && this.hits.total ? this.recordService.totalHits(this.hits.total) : 0;
}
/** Get the text for displaying results text. */
get resultsText$() {
if (this.config.resultsText) {
return this.config.resultsText(this.hits);
}
return this.total <= 1
? this.translateService.stream('{{ total }} result', { total: this.total }) // O or 1 result
: this.translateService.stream('{{ total }} results', { total: this.total });
}
/** Get showSearchInput value, given either by config or by local value. */
get showSearchInput() {
if (this.config.showSearchInput != null) {
return this.config.showSearchInput;
}
return this._showSearchInput;
}
/** Setter for `showSearchInput`. */
set showSearchInput(showSearchInput) {
this._showSearchInput = showSearchInput;
}
/** Get showLabel value, given either by config or by local value. */
get showLabel() {
if (this.config.showLabel != null) {
return this.config.showLabel;
}
return this._showLabel;
}
/** Setter for `showLabel`. */
set showLabel(showLabel) {
this._showLabel = showLabel;
}
/** Return the current page (used in pagination, has we cannot use `page` property directly) */
get currentPage() {
return this.page;
}
/**
* Used only for binding with pagination.
* Avoid side effect if "page" property is bound to pagination
* (infinite calls to get records).
* @param page - number, new page
*/
set currentPage(page) {
this.page = +page;
this._searchParamsHasChanged(false);
}
/**
* Return the list of search fields that are selected.
* @return List of selected search fields.
*/
get selectedSearchFields() {
return this.searchFields.filter((field) => field.selected);
}
/**
* Get the message if there's no record for the type.
* The message can be customized with the `noRecordMessage` property in route configuration.
* @returns A message indicating there's no record.
*/
get emptyRecordMessage() {
return this.config.noRecordMessage || this.translateService.instant('There are no records in this section');
}
/**
* Check if the current type has no record.
* @returns True if no record is found and no search query is done.
*/
get hasNoRecord() {
return this.config.showFacetsIfNoResults
? false
: !this.q && this.records.length === 0 && !this.showEmptySearchMessage;
}
/** Return a message containing the reasons why record list cannot be exported. */
get exportInfoMessage() {
return this.total === 0
? this.translateService.instant('Result list is empty.')
: this.translateService.instant('Too many items. Should be less than {{max}}.', {
max: RecordService.MAX_REST_RESULTS_SIZE,
});
}
get selectedSortValue() {
const sortParam = this.activatedRoute.snapshot?.queryParamMap?.get('sort');
return (sortParam && this.config.sortOptions)
? this.config.sortOptions.find((item) => item.value === sortParam)?.value
: null;
}
// HOOKS ======================================================
/**
* OnInit hook.
* Subscribes to the observable emitting the aggregations filters.
* Loads total count of records for each resource.
*/
ngOnInit() {
this._subscriptions.add(this.translateService.onLangChange.subscribe((lang) => {
this._loadSearchFields();
}));
this.availableTypes = this.types.filter((item) => item.hideInTabs !== true);
// Subscribe on aggregation filters changes and do search.
let first = true;
// Load the possible aggregation asked to search engine and that always need to be hide.
// DEV NOTE : These aggregation should be used by other components (using content injection)
// otherwise it doesn't matter to ask them.
if (this.config.aggregationsHide) {
this.aggregationsToHide =
typeof this.config.aggregationsHide === 'function'
? this.config.aggregationsHide()
: this.config.aggregationsHide;
}
this._subscriptions.add(this.recordSearchService.aggregationsFilters.subscribe((aggregationsFilters) => {
// No aggregations filters are set at this time, we do nothing.
if (aggregationsFilters === null) {
return;
}
aggregationsFilters.forEach((item, key) => (aggregationsFilters[key].values = item.values.sort()));
this.aggregationsFilters = aggregationsFilters;
// if it's the first value emitted, the current page is kept.
this._searchParamsHasChanged(first === false);
// not first anymore
first = false;
}));
this._subscriptions.add(this._searchParameters
.asObservable()
.pipe(
// only if the patterns changed
distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)),
// cancel previous pending requests
switchMap(() => {
if (this.aggregations != null) {
return of(null);
}
return this.config.aggregationsOrder.pipe(tap((aggregations) => {
let aggregationsExpandCfg = this.config.aggregationsExpand;
if (typeof aggregationsExpandCfg === 'function') {
aggregationsExpandCfg = aggregationsExpandCfg();
}
this.aggregations = aggregations.map((key) => {
return {
key: key.key || key,
bucketSize: this.config.aggregationsBucketSize || null,
value: { buckets: [] },
expanded: (aggregationsExpandCfg || []).includes(key),
included: ([...aggregationsExpandCfg, ...this.aggregationsToHide]).includes(key),
name: key.name || this._aggregationName(key) || null,
};
});
}));
}), tap(() => (this.hits = [])), switchMap(() => this._getRecords()))
.subscribe({
next: (records) => {
this.loaded = true;
this.hits = records.hits;
this.spinner.hide();
// Apply filters
this.aggregations$(records.aggregations).subscribe((aggregations) => {
for (const agg of this.aggregations) {
// reset aggregations
agg.loaded = false;
agg.value.buckets = [];
if (agg.key in aggregations) {
this._mapAggregation(agg, aggregations[agg.key]);
}
}
});
// Required to fire onChange event
this.aggregations = [...this.aggregations];
this._emitNewParameters();
this.recordsSearched.emit({ type: this.currentType, records });
// reload export options
this.exportOptions = this._exportFormats();
},
error: (error) => {
this.error = error;
this.spinner.hide();
}
}));
}
/**
* Methods called each time an input property is modified.
*
* If current type is changed, the configuration for the new type is loaded.
*
* The first time current type is changed, the types property is set in the
* RecordUiServices. A better way to do that is to detect route change in the
* service, but at this time it is done like that.
*
* This method does a search at the end but only if the search parameters have
* changed. We want to avoid a double search when the following process is
* done:
*
* 1. A parameter is changed in the search interface.
* 2. Local property is updated.
* 3. A search is done.
* 4. The component outputs the search parameters.
* 5. The parent component (RecordSearchPage) updates the URL
* 6. The parent component detects route parameters changes.
* 7. The parent component update the input value of the component.
* 8. This method is triggered.
*
* @param changes Object containing all the changed properties.
*/
ngOnChanges(changes) {
// Current type has changed
if (changes.currentType) {
if (changes.currentType.firstChange) {
// Load all configuration types, only during the first change
this.recordUiService.types = this.types;
}
// if the "type" property is changed in input, but the change is not
// triggered by clicking on a tab (which already load configuration),
// we reload configuration.
// If no configuration is loaded, we load it, too.
if (this.config === null ||
(changes.currentType.currentValue != null && changes.currentType.currentValue !== this.config.key)) {
this._loadConfigurationForType(this.currentType);
}
}
// If it's the first change, we don't do a search, it's delegated to the
// aggregations filters subscription.
if (changes[Object.keys(changes)[0]].firstChange === false) {
// Get records and reset page only if page has not changed
this._searchParamsHasChanged(!('page' in changes));
}
}
/**
* OnDestroy hook
* Unsubscribes from the observable of the aggregations filters.
*/
ngOnDestroy() {
this._subscriptions.unsubscribe();
}
// COMPONENT FUNCTIONS ======================================================
/**
* Internal notification that notify the search parameters has changed.
* @param resetPage reset the pager to the first page
*/
_searchParamsHasChanged(resetPage = true) {
if (resetPage) {
this.page = 1;
}
// Deep copy is sent, to avoid the reference (source object) to be
// accidentally changed.
this._searchParameters.next(cloneDeep(this._serializeSearchParameters()));
}
/** Move to the top of the page if you change page */
pageChanged() {
this.topScroll.nativeElement.scrollIntoView({ behavior: 'smooth' });
}
/**
* Change number of items per page value.
* @param event - Event, dom event triggered
* @param size - number, new page size
*/
changeSize(event, size) {
event.preventDefault();
this.size = size;
this._searchParamsHasChanged();
}
/**
* Change sorting.
* @param sort sort name.
*/
changeSorting(sort) {
this.sort = sort;
this._searchParamsHasChanged();
}
/**
* Change query text.
* @param event - string, new query text
*/
searchByQuery(event) {
// If empty search is not allowed and query is empty, the search is not processed.
if (this.config.allowEmptySearch === false && !event) {
this.showEmptySearchMessage = true;
return;
}
this.q = event;
this._setDefaultSort();
this._searchParamsHasChanged();
// Build aggregations to used.
// This is a combination of `defaultSearchInputFilters` and persistent aggregation filters.
const aggregations = new Set(this._extractPersistentAggregationsFilters());
this.aggregationsFilters.forEach((aggregation) => aggregations.add(aggregation));
this.recordSearchService.setAggregationsFilters(Array.from(aggregations), true);
}
/**
* Change type of records.
* @param type - resource type name.
*/
changeType(type) {
this.currentType = type;
this._loadConfigurationForType(this.currentType);
this.aggregationsFilters = [];
this._searchParamsHasChanged();
this.recordSearchService.setAggregationsFilters([]);
}
/**
* Delete a record by its PID.
* @param event - object
*/
deleteRecord(event) {
const type = event.type ?? this.currentType;
this.recordUiService
.deleteRecord(type, event.pid)
.pipe(switchMap((result) => {
if (result === true) {
this.page = 1;
return this._getRecords();
}
return of(null);
}))
.subscribe((records) => {
if (records != null) {
this.hits = records.hits;
this.spinner.hide();
}
});
// update main counter
this.config.total--;
}
/**
* Show a modal containing message given in parameter.
* @param message - message to display into modal
*/
showDeleteMessage(message) {
this.recordUiService.showDeleteMessage(message);
}
/**
* Get component view for the current resource type.
* @return A component for displaying result item.
*/
getResultItemComponentView() {
return this.config.component ? this.config.component : null;
}
/**
* Get Export formats for the current resource given by configuration.
* @return Array of export format to generate an `export as` button or an empty array.
*/
_exportFormats() {
if (!this.config || !this.config.exportFormats) {
return [];
}
return this.config.exportFormats.map((format) => {
return {
label: format.label,
url: this.getExportFormatUrl(format),
disabled: !this.canExport(format),
disabled_message: this.exportInfoMessage,
};
});
}
/**
* Get export format url.
* @param format - export format object
* @return formatted url for an export format.
*/
getExportFormatUrl(format) {
const queryParams = Object.keys(this.activatedRoute.snapshot.queryParams);
// TODO: maybe we can use URLSerializer to build query string
const baseUrl = format.endpoint ? format.endpoint : this.apiService.getEndpointByType(this._currentIndex());
let url = `${baseUrl}?q=${encodeURIComponent(this.q)}&format=${format.format}`;
// check if max rest result size is disabled
if (!format.disableMaxRestResultsSize) {
url += `&size=${RecordService.MAX_REST_RESULTS_SIZE}`;
}
// preFilters
if (this.config && this.config.preFilters) {
for (const [key, value] of Object.entries(this.config.preFilters)) {
// force value to an array
const values = !Array.isArray(value) ? [value] : value;
values.map((v) => {
// We check whether the parameter exists in the current url.
// If it does, we don't add the preFilter parameter.
if (!queryParams.includes(key)) {
url += `&${key}=${v}`;
}
});
}
}
// aggregations
if (this.aggregationsFilters) {
this.aggregationsFilters.map((filter) => {
filter.values.map((v) => {
url += `&${filter.key}=${v}`;
});
});
}
return url;
}
/**
* Check if a record list can be exported
* @param format - export format object
* @return Boolean
*/
canExport(format) {
return format.hasOwnProperty('disableMaxRestResultsSize') && format.disableMaxRestResultsSize
? this.total > 0
: 0 < this.total && this.total < RecordService.MAX_REST_RESULTS_SIZE;
}
/**
* Check if a record can be updated
* @param record - object, record to check
* @return Observable
*/
canUpdateRecord$(record) {
return this.recordUiService.canUpdateRecord$(record, this.currentType);
}
/**
* Check if a record can be deleted
* @param record - object, record to check
* @return Observable
*/
canDeleteRecord$(record) {
return this.recordUiService.canDeleteRecord$(record, this.currentType);
}
/**
* Check if a record can be used
* @param record - object, record to check
* @return Observable
*/
canUseRecord$(record) {
return this.recordUiService.canUseRecord$(record, this.currentType);
}
/**
* Filter the aggregations with given configuration function.
* If no configuration is given, return the original aggregations.
* @param records - Result records
* @return Observable containing aggregations corresponding to actual records.
*/
aggregations$(aggregations) {
if (this.config.aggregations) {
return this.config.aggregations(aggregations);
}
else {
return of(aggregations);
}
}
/**
* Returns an observable which emits the URL value for given record.
* In case record cannot be read, returns null.
* @param record - Generate detail URL for this record.
* @return Observable emitting detail URL object
*/
resolveDetailUrl$(record) {
const url = { link: `detail/${record.id}`, external: false };
if (this.detailUrl) {
url.link = this.detailUrl.replace(':type', this._currentIndex()).replace(':pid', record.id);
url.external = true;
}
if (!this.config.canRead) {
return of(url);
}
return this.recordUiService.canReadRecord$(record, this.currentType).pipe(map$4((status) => {
if (status.can === false) {
return null;
}
return url;
}));
}
searchInField(event, path) {
this.searchFields = this.searchFields.map((item) => {
if (item.path === path) {
item.selected = event.checked;
}
return item;
});
// If query string is specified, search is processed.
if (this.q) {
this.searchByQuery(this.q);
}
}
onChangeSearchFilter(event) {
const values = [];
if (event.filterKey === 'simple') {
values.push(event.checked ? '0' : '1');
}
else if (event.checked) {
values.push(String(event.checked));
}
this.recordSearchService.updateAggregationFilter(event.filterKey, values);
}
/**
* Check if a filter is selected.
*
* @param filter SearchFilter
* @returns true if the given filter is selected.
*/
isFilterActive(filter) {
return this.aggregationsFilters
? this.aggregationsFilters.some((item) => item.key === filter.filter && item.values.includes(String(filter.value)))
: false;
}
/**
* Load buckets for the given aggregation.
* @param event - Object outputted when an aggregation is clicked.
*/
loadAggregationBuckets(event) {
// the aggregation is collapsed, buckets are not loaded.
if (event.expanded === false) {
return;
}
const aggregation = this.aggregations.find((item) => item.key === event.key);
// No aggregation found or buckets are already loaded.
if (!aggregation || aggregation.loaded) {
return;
}
// Get buckets for the aggregation
this._getRecords(1).subscribe((records) => {
this.aggregations$(records.aggregations).subscribe((aggregations) => {
if (aggregations[event.key]) {
this._mapAggregation(aggregation, aggregations[event.key]);
}
// Required to fire onChange event
this.aggregations = [...this.aggregations];
this.spinner.hide();
});
});
}
/**
* Search for records.
* @param size - number : force the number of records to return. If `null` the default component `size` attribute will be used.
*/
_getRecords(size = null) {
// Build query string
const q = this._buildQueryString();
// Empty search is not allowed and query is empty, search in backend is not
// processed.
if (this.config.allowEmptySearch === false && !q) {
this.showEmptySearchMessage = true;
return of({ hits: { hits: [], total: 0 }, aggregations: {} });
}
else {
this.showEmptySearchMessage = false;
}
this.spinner.show();
// Check remove filter from preFilters if it is already present in
// the aggs filters.
const preFilters = {};
const aggsKeys = [];
this.aggregationsFilters.map((agg) => aggsKeys.push(agg.key));
for (const [key, value] of Object.entries(this.config.preFilters || {})) {
if (!aggsKeys.includes(key)) {
preFilters[key] = value;
}
}
return this.recordService.getRecords(this._currentIndex(), q, this.page, size || this.size, this.aggregationsFilters || [], preFilters, this.config.listHeaders || null, this.sort, this._getFacetsParameter());
}
/**
* Emit new parameters when a search is done.
*/
_emitNewParameters() {
this.parametersChanged.emit({
q: this.q,
page: this.page,
size: this.size,
currentType: this.config.key,
aggregationsFilters: this.aggregationsFilters,
sort: this.sort,
});
}
/**
* Get configuration for the current resource type.
* @param type Type of resource
*/
_loadConfigurationForType(type) {
const q = this._buildQueryString();
this.config = this.recordUiService.getResourceConfig(type);
this.recordUiService.canAddRecord$(type).subscribe((result) => {
this.addStatus = result;
});
this._loadSearchFields();
// Build aggregations.
if (this.config.aggregationsOrder) {
if (!isObservable(this.config.aggregationsOrder)) {
this.config.aggregationsOrder = of(this.config.aggregationsOrder);
}
}
else {
this.config.aggregationsOrder = of([]);
}
// reset aggregations
this.aggregations = null;
// Sort options
this._setDefaultSort();
// load search filters
this.searchFilters = this.config.searchFilters || [];
// load export options
this.exportOptions = this._exportFormats();
// Update filters with default search filters only if the q parameter is empty
if ((q === null || q.trim().length === 0) && this.config.defaultSearchInputFilters) {
this.config.defaultSearchInputFilters.forEach((filter) => {
this.recordSearchService.updateAggregationFilter(filter.key, filter.values);
});
}
}
/**
* Get Resource config
* @param paramName - Name of parameter
* @param defaultValue - Default value is returned if the parameter is not defined
* @return A config value or the given default value instead
*/
_getResourceConfig(paramName, defaultValue) {
return (paramName in this.config) ? this.config[paramName] : defaultValue;
}
/**
* Serialize all the search parameters with JSON.stringify method.
* @return The serialized string.
*/
_serializeSearchParameters() {
return {
currentType: this.config.key,
index: this._currentIndex(),
q: this.q,
page: this.page,
size: this.size,
sort: this.sort,
aggregationsFilters: this.aggregationsFilters,
searchFields: this.searchFields,
searchFilters: this.searchFilters,
};
}
/**
* Load search fields stored in configuration and assign to them the
* `selected` property.
*
* @returns void
*/
_loadSearchFields() {
// No search fields, reset previous stored and return.
if (!this.config.searchFields) {
this.searchFields = [];
return;
}
// Store search fields.
this.searchFields = this.config.searchFields.map((field) => {
field.label = this.translateService.instant(field.label);
if (!field.selected) {
field.selected = false;
}
return field;
});
}
/**
* Build query string with possibly search fields.
*
* @return Final query string.
*/
_buildQueryString() {
// If query is empty or no selected fields found, the initial query string
// is returned.
if (!this.q || this.selectedSearchFields.length === 0) {
return this.q;
}
// Loop over select fields and add them to final query string.
const queries = [];
this.selectedSearchFields.forEach((field) => {
queries.push(`${field.path}:(${this.q})`);
});
return queries.join(' ');
}
/**
* Return the name of the index defined either by the index key or key.
*
* @return string, current index defined by keys index or key
*/
_currentIndex() {
if (this.config == null) {
return null;
}
return this.config.index || this.config.key;
}
/**
* Get personalized name for current aggregation
* @param key - string, aggregation key
* @return string or null
*/
_aggregationName(key) {
return this.config.aggregationsName && key in this.config.aggregationsName
? this.config.aggregationsName[key]
: null;
}
/**
* Extract persistent search filters on current url
* @return Array of aggregations filter
*/
_extractPersistentAggregationsFilters() {
const persistent = [];
this._flatSearchFilters()
.filter((filter) => filter.persistent === true)
.forEach((filter) => {
if (this.activatedRoute.snapshot.queryParams.hasOwnProperty(filter.filter)) {
const data = this.activatedRoute.snapshot.queryParams[filter.filter];
persistent.push({
key: filter.filter,
values: Array.isArray(data) ? data : [data],
});
}
});
return persistent;
}
/**
* Make all search filters on same array level
* @returns - A filters array
*/
_flatSearchFilters() {
const flatFilters = [];
this.searchFilters.forEach((searchFilter) => {
if (searchFilter.filters) {
searchFilter.filters.forEach((filter) => flatFilters.push(filter));
}
else {
flatFilters.push(searchFilter);
}
});
return flatFilters;
}
/**
* Map aggregation records data to corresponding aggregation.
*
* @param aggregation Aggregation object.
* @param recordsAggregation Aggregation retrieved from record.
*/
_mapAggregation(aggregation, recordsAggregation) {
aggregation.doc_count = recordsAggregation.doc_count || null;
aggregation.type = recordsAggregation.type || 'terms';
aggregation.config = recordsAggregation.config || null;
if (!aggregation.name && recordsAggregation.name) {
aggregation.name = recordsAggregation.name;
}
if (recordsAggregation.buckets) {
aggregation.value.buckets = recordsAggregation.buckets;
if (recordsAggregation.doc_count != null) {
aggregation.doc_count = recordsAggregation.doc_count;
}
aggregation.value.buckets.map((bucket) => this.processBuckets(bucket, aggregation.key));
}
aggregation.loaded = true;
}
/**
* Enrich the bucket with several properties: indeterminate, parent, aggregationKey.
*
* @param bucket elastic bucket to process
* @param aggregationKey bucket parent key
* @returns the indeterminate state of the bucket
*/
processBuckets(bucket, aggregationKey) {
// checkbox indeterminate state
bucket.indeterminate = false;
bucket.aggregationKey = aggregationKey;
for (const k of Object.keys(bucket).filter((key) => bucket[key].buckets)) {
for (const childBucket of bucket[k].buckets) {
// store the parent: useful to remove parent filters
childBucket.parent = bucket;
// store the aggregation key as we re-organize the bucket structure
bucket.indeterminate ||= this.recordSearchService.hasFilter(k, childBucket.key);
// do not change the order of the boolean expression to force processBucket over all
// recursion steps
bucket.indeterminate = this.processBuckets(childBucket, k) || bucket.indeterminate;
}
}
return bucket.indeterminate;
}
/**
* Compile facets keys to get only 'included' facets or having a filter selected.
* @returns List of facets.
*/
_getFacetsParameter() {
const facets = [];
this.aggregations.forEach((agg) => {
if (agg.included === true ||
agg.expanded ||
this.aggregationsFilters.some((filter) => filter.key === agg.key)) {
facets.push(agg.key);
}
});
return facets;
}
/** Set the default sort. */
_setDefaultSort() {
if (this.sort != null) {
return;
}
if (this.config.sortOptions) {
const defaultSortValue = this.q ? 'defaultQuery' : 'defaultNoQuery';
this.config.sortOptions.forEach((option) => {
if (option[defaultSortValue] === true) {
this.sort = option.value;
}
});
}
}
/**
* Show the filter's section
* @param searchFilterSection - Collection of filter
* @returns true if the filter's section is show
*/
showFilterSection(searchFilterSection) {
return searchFilterSection.filters.some((filter) => {
return this.config.allowEmptySearch ? true : (this.q && filter.showIfQuery === true) || !filter?.showIfQuery;
});
}
/**
* Show the filter
* @param searchFilter - search Filter
* @returns true if the filter is show
*/
showFilter(searchFilter) {
if (this.config.allowEmptySearch) {
return true;
}
if (!this.q) {
return !(searchFilter.showIfQuery === true);
}
else {
return searchFilter.showIfQuery === true || !searchFilter?.showIfQuery;
}
}
paginatorChange(event) {
this.page = ++event.page;
this.size = event.rows;
this._searchParamsHasChanged(false);
}
/**
* Remove filter.
*
* @param filter - the filter to remove
*/
removeFilter(filter) {
this.recordSearchService.removeFilter(filter.aggregationKey, filter.key, true);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: RecordSearchComponent, isStandalone: false, selector: "ng-core-record-search", inputs: { currentType: "currentType", q: "q", page: "page", size: "size", sort: "sort", adminMode: "adminMode", detailUrl: "detailUrl", types: "types", showSearchInput: "showSearchInput", showLabel: "showLabel" }, outputs: { parametersChanged: "parametersChanged", recordsSearched: "recordsSearched" }, viewQueries: [{ propertyName: "topScroll", first: true, predicate: ["topScrollAnchor"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2019-2024 RERO\n Copyright (C) 2019-2023 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (error) {\n<ng-core-error [error]=\"error\"></ng-core-error>\n} @else {\n<span #topScrollAnchor></span>\n<ng-core-search-tabs [types]=\"availableTypes\" (onChangeType)=\"changeType($event)\"/>\n<div class=\"core:flex core:flex-col core:gap-2\">\n @if (availableTypes.length === 1 && showLabel) {\n <h5>{{ availableTypes[0].label | translate }}</h5>\n }\n <ng-content select=\"[header]\"></ng-content>\n <div class=\"core:flex core:gap-2\">\n @if (showSearchInput) {\n <ng-core-search-input\n class=\"core:grow\"\n [placeholder]=\"'search' | translate | ucfirst\"\n [searchText]=\"q\"\n [displayLabel]=\"false\"\n (search)=\"searchByQuery($event)\"\n [focus]=\"true\"\n />\n }\n <ng-content select=\"[afterInputSearch]\"/>\n </div>\n @if (loaded && hasNoRecord) {\n <p-message severity=\"info\">\n {{ emptyRecordMessage }}\n </p-message>\n @if (adminMode.can && addStatus.can) {\n <p-button\n id=\"search-add-button\"\n [label]=\"'Add' | translate\"\n icon=\"fa fa-plus\"\n [routerLink]=\"addStatus.routerLink || ['new']\"\n />\n }\n } @else {\n <div class=\"core:flex core:items-stretch core:w-full\">\n @if (resultsText$ | async; as resultsText) {\n <div class=\"core:flex core:items-center core:w-full\">\n <strong>{{ resultsText }}</strong>\n </div>\n }\n <div class=\"core:flex core:items-center core:justify-end core:w-full\">\n @if (adminMode.can && addStatus.can) {\n <p-button\n id=\"search-add-button\"\n [label]=\"'Add' | translate\"\n icon=\"fa fa-plus\"\n [routerLink]=\"addStatus.routerLink || ['new']\"\n />\n }\n <!-- Sorting -->\n @if (config?.sortOptions?.length > 0) {\n <ng-core-menu-sort class=\"core:ml-1\" [selectedValue]=\"selectedSortValue\" [config]=\"config.sortOptions\" (onChange)=\"changeSorting($event)\" />\n }\n <!-- EXPORT BUTTON -->\n <ng-core-export-button class=\"core:ml-1\"[exportOptions]=\"exportOptions\"/>\n <!-- END EXPORT BUTTON -->\n </div>\n </div>\n <ng-content select=\"[beforeResult]\"></ng-content>\n <div class=\"core:grid core:grid-cols-12 core:gap-4\">\n <ng-core-list-filters\n [aggregations]=\"aggregations\"\n class=\"core:col-span-12\"\n [aggregationsFilters]=\"aggregationsFilters\"\n [searchFilters]=\"searchFilters\"\n (remove)=\"removeFilter($event)\"\n />\n <ng-content select=\"[top-search-result]\"></ng-content>\n @if ((aggregations && aggregations.length) || searchFields.length > 0 || searchFilters.length > 0) {\n <div class=\"core:md:col-span-5 core:lg:col-span-3 core:col-span-12\">\n @if (searchFields.length > 0 && q) {\n <div class=\"core:mb-4\">\n @for (searchField of searchFields; track $index) {\n <div class=\"core:flex core:gap-2 core:items-center\">\n <p-toggleswitch (onChange)=\"searchInField($event, searchField.path)\" />\n <label>{{ searchField.label | translate }}</label>\n </div>\n }\n </div>\n }\n\n <ng-core-search-filters\n [searchFilters]=\"searchFilters\"\n [config]=\"config\"\n [query]=\"q\"\n styleClassSection=\"core:my-2\"\n (onChange)=\"onChangeSearchFilter($event)\"\n />\n\n @if (!showEmptySearchMessage || q) {\n <ng-content select=\"[top-facets]\"/>\n @for (item of aggregations; track item.key) {\n <div>\n @if (!aggregationsToHide.includes(item.key)) {\n <ng-core-record-search-aggregation\n [aggregation]=\"item\"\n [aggregationsFilters]=\"aggregationsFilters\"\n (aggregationClicked)=\"loadAggregationBuckets($event)\"\n />\n }\n </div>\n } }\n </div>\n }\n <div id=\"recordlist\" class=\"core:md:col-span-7 core:lg:col-span-9 core:col-span-12\">\n @if (showEmptySearchMessage) {\n <p-message severity=\"info\">\n {{ 'Enter a query to get results.' | translate }}\n </p-message>\n }\n <ng-content select=\"[top-result]\"/>\n <p-dataView [value]=\"records\" [emptyMessage]=\"' '\">\n <ng-template #list let-records>\n <ul class=\"core:list-none\">\n @for (record of records; track record.metadata.pid; let first = $first) {\n <li class=\"core:py-2 core:md:py-6\" [ngClass]=\"{ 'core:border-t-1 core:border-surface': !first }\">\n <ng-core-record-search-result\n [adminMode]=\"adminMode\"\n [record]=\"record\"\n [type]=\"currentType\"\n [itemViewComponent]=\"getResultItemComponentView()\"\n [canUpdate$]=\"canUpdateRecord$(record)\"\n [canDelete$]=\"canDeleteRecord$(record)\"\n [canUse$]=\"canUseRecord$(record)\"\n [detailUrl$]=\"resolveDetailUrl$(record)\"\n (deletedRecord)=\"deleteRecord($event)\"\n (deleteMessageEvent)=\"showDeleteMessage($event)\"\n />\n </li>\n }\n </ul>\n </ng-template>\n </p-dataView>\n\n <ng-core-paginator\n [alwaysShow]=\"showPagination\"\n [currentPage]=\"currentPage\"\n [pageLinkSize]=\"paginationMaxSize\"\n [totalRecords]=\"total\"\n [rows]=\"size\"\n [rowsPerPageOptions]=\"config?.pagination?.rowsPerPageOptions || [10, 20, 50]\"\n [showCurrentPageReport]=\"config?.pagination?.pageReport || false\"\n [showFirstLastIcon]=\"paginationBoundaryLinks\"\n (rowPageChange)=\"paginatorChange($event)\"\n />\n <ng-content select=\"[footer-result]\"/>\n <ng-content/>\n </div>\n </div>\n <ng-content select=\"[footer-search-result]\"/>\n }\n</div>\n}\n", dependencies: [{ kind: "component", type: i1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i2$1.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }, { kind: "component", type: i1$4.ToggleSwitch, selector: "p-toggleswitch, p-toggleSwitch, p-toggle-switch", inputs: ["style", "styleClass", "tabindex", "inputId", "name", "disabled", "readonly", "trueValue", "falseValue", "ariaLabel", "ariaLabelledBy", "autofocus"], outputs: ["onChange"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SearchInputComponent, selector: "ng-core-search-input", inputs: ["inputId", "displayLabel", "placeholder", "searchText", "trimQueryString", "disabled", "focus"], outputs: ["search"] }, { kind: "component", type: ErrorComponent, selector: "ng-core-error", inputs: ["error"] }, { kind: "directive", type: i2$8.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: i8.DataView, selector: "p-dataView, p-dataview, p-data-view", inputs: ["paginator", "rows", "totalRecords", "pageLinks", "rowsPerPageOptions", "paginatorPosition", "paginatorStyleClass", "alwaysShowPaginator", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showFirstLastIcon", "showPageLinks", "lazy", "lazyLoadOnInit", "emptyMessage", "style", "styleClass", "gridStyleClass", "trackBy", "filterBy", "filterLocale", "loading", "loadingIcon", "first", "sortField", "sortOrder", "value", "layout"], outputs: ["onLazyLoad", "onPage", "onSort", "onChangeLayout"] }, { kind: "component", type: RecordSearchResultComponent, selector: "ng-core-record-search-result", inputs: ["record", "type", "itemViewComponent", "adminMode", "canUpdate$", "canDelete$", "canUse$", "aggregations", "detailUrl$"], outputs: ["deletedRecord", "deleteMessageEvent"] }, { kind: "component", type: RecordSearchAggregationComponent, selector: "ng-core-record-search-aggregation", inputs: ["aggregation", "aggregationsFilters"], outputs: ["aggregationClicked"] }, { kind: "component", type: ExportButtonComponent, selector: "ng-core-export-button", inputs: ["exportOptions"] }, { kind: "component", type: ListFiltersComponent, selector: "ng-core-list-filters", inputs: ["aggregations", "aggregationsFilters", "searchFilters"], outputs: ["remove"] }, { kind: "component", type: PaginatorComponent, selector: "ng-core-paginator", inputs: ["alwaysShow", "currentPage", "pageLinkSize", "rows", "rowsPerPageOptions", "showCurrentPageReport", "showFirstLastIcon", "totalRecords"], outputs: ["rowPageChange"] }, { kind: "component", type: MenuSortComponent, selector: "ng-core-menu-sort", inputs: ["config", "selectedValue"], outputs: ["onChange"] }, { kind: "component", type: SearchFiltersComponent, selector: "ng-core-search-filters", inputs: ["searchFilters", "config", "query", "styleClassSection"], outputs: ["onChange"] }, { kind: "component", type: SearchTabsComponent, selector: "ng-core-search-tabs", inputs: ["types", "currentType"], outputs: ["onChangeType", "currentTypeChange"] }, { kind: "pipe", type: i1$2.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.TranslatePipe, name: "translate" }, { kind: "pipe", type: UpperCaseFirstPipe, name: "ucfirst" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-record-search', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2019-2024 RERO\n Copyright (C) 2019-2023 UCLouvain\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n@if (error) {\n<ng-core-error [error]=\"error\"></ng-core-error>\n} @else {\n<span #topScrollAnchor></span>\n<ng-core-search-tabs [types]=\"availableTypes\" (onChangeType)=\"changeType($event)\"/>\n<div class=\"core:flex core:flex-col core:gap-2\">\n @if (availableTypes.length === 1 && showLabel) {\n <h5>{{ availableTypes[0].label | translate }}</h5>\n }\n <ng-content select=\"[header]\"></ng-content>\n <div class=\"core:flex core:gap-2\">\n @if (showSearchInput) {\n <ng-core-search-input\n class=\"core:grow\"\n [placeholder]=\"'search' | translate | ucfirst\"\n [searchText]=\"q\"\n [displayLabel]=\"false\"\n (search)=\"searchByQuery($event)\"\n [focus]=\"true\"\n />\n }\n <ng-content select=\"[afterInputSearch]\"/>\n </div>\n @if (loaded && hasNoRecord) {\n <p-message severity=\"info\">\n {{ emptyRecordMessage }}\n </p-message>\n @if (adminMode.can && addStatus.can) {\n <p-button\n id=\"search-add-button\"\n [label]=\"'Add' | translate\"\n icon=\"fa fa-plus\"\n [routerLink]=\"addStatus.routerLink || ['new']\"\n />\n }\n } @else {\n <div class=\"core:flex core:items-stretch core:w-full\">\n @if (resultsText$ | async; as resultsText) {\n <div class=\"core:flex core:items-center core:w-full\">\n <strong>{{ resultsText }}</strong>\n </div>\n }\n <div class=\"core:flex core:items-center core:justify-end core:w-full\">\n @if (adminMode.can && addStatus.can) {\n <p-button\n id=\"search-add-button\"\n [label]=\"'Add' | translate\"\n icon=\"fa fa-plus\"\n [routerLink]=\"addStatus.routerLink || ['new']\"\n />\n }\n <!-- Sorting -->\n @if (config?.sortOptions?.length > 0) {\n <ng-core-menu-sort class=\"core:ml-1\" [selectedValue]=\"selectedSortValue\" [config]=\"config.sortOptions\" (onChange)=\"changeSorting($event)\" />\n }\n <!-- EXPORT BUTTON -->\n <ng-core-export-button class=\"core:ml-1\"[exportOptions]=\"exportOptions\"/>\n <!-- END EXPORT BUTTON -->\n </div>\n </div>\n <ng-content select=\"[beforeResult]\"></ng-content>\n <div class=\"core:grid core:grid-cols-12 core:gap-4\">\n <ng-core-list-filters\n [aggregations]=\"aggregations\"\n class=\"core:col-span-12\"\n [aggregationsFilters]=\"aggregationsFilters\"\n [searchFilters]=\"searchFilters\"\n (remove)=\"removeFilter($event)\"\n />\n <ng-content select=\"[top-search-result]\"></ng-content>\n @if ((aggregations && aggregations.length) || searchFields.length > 0 || searchFilters.length > 0) {\n <div class=\"core:md:col-span-5 core:lg:col-span-3 core:col-span-12\">\n @if (searchFields.length > 0 && q) {\n <div class=\"core:mb-4\">\n @for (searchField of searchFields; track $index) {\n <div class=\"core:flex core:gap-2 core:items-center\">\n <p-toggleswitch (onChange)=\"searchInField($event, searchField.path)\" />\n <label>{{ searchField.label | translate }}</label>\n </div>\n }\n </div>\n }\n\n <ng-core-search-filters\n [searchFilters]=\"searchFilters\"\n [config]=\"config\"\n [query]=\"q\"\n styleClassSection=\"core:my-2\"\n (onChange)=\"onChangeSearchFilter($event)\"\n />\n\n @if (!showEmptySearchMessage || q) {\n <ng-content select=\"[top-facets]\"/>\n @for (item of aggregations; track item.key) {\n <div>\n @if (!aggregationsToHide.includes(item.key)) {\n <ng-core-record-search-aggregation\n [aggregation]=\"item\"\n [aggregationsFilters]=\"aggregationsFilters\"\n (aggregationClicked)=\"loadAggregationBuckets($event)\"\n />\n }\n </div>\n } }\n </div>\n }\n <div id=\"recordlist\" class=\"core:md:col-span-7 core:lg:col-span-9 core:col-span-12\">\n @if (showEmptySearchMessage) {\n <p-message severity=\"info\">\n {{ 'Enter a query to get results.' | translate }}\n </p-message>\n }\n <ng-content select=\"[top-result]\"/>\n <p-dataView [value]=\"records\" [emptyMessage]=\"' '\">\n <ng-template #list let-records>\n <ul class=\"core:list-none\">\n @for (record of records; track record.metadata.pid; let first = $first) {\n <li class=\"core:py-2 core:md:py-6\" [ngClass]=\"{ 'core:border-t-1 core:border-surface': !first }\">\n <ng-core-record-search-result\n [adminMode]=\"adminMode\"\n [record]=\"record\"\n [type]=\"currentType\"\n [itemViewComponent]=\"getResultItemComponentView()\"\n [canUpdate$]=\"canUpdateRecord$(record)\"\n [canDelete$]=\"canDeleteRecord$(record)\"\n [canUse$]=\"canUseRecord$(record)\"\n [detailUrl$]=\"resolveDetailUrl$(record)\"\n (deletedRecord)=\"deleteRecord($event)\"\n (deleteMessageEvent)=\"showDeleteMessage($event)\"\n />\n </li>\n }\n </ul>\n </ng-template>\n </p-dataView>\n\n <ng-core-paginator\n [alwaysShow]=\"showPagination\"\n [currentPage]=\"currentPage\"\n [pageLinkSize]=\"paginationMaxSize\"\n [totalRecords]=\"total\"\n [rows]=\"size\"\n [rowsPerPageOptions]=\"config?.pagination?.rowsPerPageOptions || [10, 20, 50]\"\n [showCurrentPageReport]=\"config?.pagination?.pageReport || false\"\n [showFirstLastIcon]=\"paginationBoundaryLinks\"\n (rowPageChange)=\"paginatorChange($event)\"\n />\n <ng-content select=\"[footer-result]\"/>\n <ng-content/>\n </div>\n </div>\n <ng-content select=\"[footer-search-result]\"/>\n }\n</div>\n}\n" }]
}], propDecorators: { topScroll: [{
type: ViewChild,
args: ['topScrollAnchor']
}], currentType: [{
type: Input
}], q: [{
type: Input
}], page: [{
type: Input
}], size: [{
type: Input
}], sort: [{
type: Input
}], adminMode: [{
type: Input
}], detailUrl: [{
type: Input
}], types: [{
type: Input
}], parametersChanged: [{
type: Output
}], recordsSearched: [{
type: Output
}], showSearchInput: [{
type: Input
}], showLabel: [{
type: Input
}] } });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordSearchPageComponent {
constructor() {
this.route = inject(ActivatedRoute);
this.router = inject(Router);
this.recordSearchService = inject(RecordSearchService);
this.recordUiService = inject(RecordUiService);
/**
* Current selected resource type
*/
this.currentType = 'documents';
/**
* URL to notice detail
*/
this.detailUrl = null;
/**
* Display search input
*/
this.showSearchInput = true;
/**
* Admin mode (edit, remove, add, ...)
*/
this.adminMode = {
can: true,
message: ''
};
/**
* Define the current record's page
*/
this.page = 1;
/**
* Define the number of records per page
*/
this.size = 10;
/**
* Search query
*/
this.q = '';
/**
* Define the sort order of resulting records. It takes a string value
* representing the property which is used to sort the records.
* If a minus is put before the value, the sort is reversed.
*/
this.sort = null;
/**
* Types of resources available
*/
this.types = [{ key: 'documents', label: 'Documents' }];
}
/**
* Component initialization.
*
* Subscribes to changes of route parameters and query parameters for
* updating the search parameters and sending them to child component
* (RecordSearchComponent).
*/
ngOnInit() {
this.routeParametersSubscription = combineLatest([this.route.paramMap, this.route.queryParamMap]).subscribe(([paramMap, queryParams]) => {
// store current type of resource
this.currentType = paramMap.get('type');
this.recordUiService.types = this.route.snapshot.data.types;
const config = this.recordUiService.getResourceConfig(this.currentType);
// Stores query parameters
this.q = queryParams.get('q') || '';
this.size = queryParams.get('size') ? +queryParams.get('size') : 10;
this.page = queryParams.get('page') ? +queryParams.get('page') : 1;
this._setDefaultSort(config, queryParams.get('sort'));
// loops over all aggregations filters and stores them.
const aggregationsFilters = [];
queryParams.keys.forEach((key) => {
if (['q', 'page', 'size', 'sort'].includes(key) === false) {
const values = queryParams.getAll(key);
aggregationsFilters.push({ key, values });
}
});
// Add filter parameter value
if (config.searchFilters) {
config.searchFilters.forEach((filter) => {
if (queryParams.get(filter.filter) === null && filter.disabledValue) {
aggregationsFilters.push({ key: filter.filter, values: [filter.disabledValue] });
}
});
}
// update the facet filters given the URL params
this.recordSearchService.setAggregationsFilters(aggregationsFilters);
});
// Store configuration data
const { data } = this.route.snapshot;
if (data.types) {
this.types = data.types;
}
if (data.showSearchInput != null) {
this.showSearchInput = data.showSearchInput;
}
if (data.adminMode) {
data.adminMode().subscribe((as) => this.adminMode = as);
}
if (data.detailUrl) {
this.detailUrl = data.detailUrl;
}
}
/**
* Component destruction.
*
* Unsubscribes from the observables of the route parameters.
*/
ngOnDestroy() {
this.routeParametersSubscription.unsubscribe();
}
/**
* Update URL accordingly to parameters given.
* @param parameters Parameters to put in url or query string
*/
updateUrl(parameters) {
const queryParams = {
q: parameters.q,
page: parameters.page,
size: parameters.size,
sort: parameters.sort
};
for (const filter of parameters.aggregationsFilters) {
// We need to loop over each value and insert it on beginning of the array
// instead of assign values directly. Otherwise, angular router doesn't
// detect the changes. It's certainly a bug in angular.
queryParams[filter.key] = [];
for (const value of filter.values) {
queryParams[filter.key].unshift(value);
}
}
this.router.navigate([this.getCurrentUrl(parameters.currentType)], { queryParams, relativeTo: this.route });
}
/**
* Return current URL after removing query parameters and updating resource type.
*
* @returns Updated url without query string
*/
getCurrentUrl(type) {
const segments = this.router.parseUrl(this.router.url).root.children.primary.segments.map(it => it.path);
segments[segments.length - 1] = type;
return '/' + segments.join('/');
}
/**
* Sets the default sort for resource type.
*
* @param config Configuration object.
*/
_setDefaultSort(config, sortParam) {
if (sortParam) {
this.sort = sortParam;
return;
}
this.sort = null;
if (!config.sortOptions) {
return;
}
const defaultSortValue = this.q ? 'defaultQuery' : 'defaultNoQuery';
config.sortOptions.forEach((option) => {
if (option[defaultSortValue] === true) {
this.sort = option.value;
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: RecordSearchPageComponent, isStandalone: false, selector: "ng-core-record-search-page", ngImport: i0, template: "<!--\n RERO angular core\n Copyright (C) 2020 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<ng-core-record-search class=\"core:my-1\" [adminMode]=\"adminMode\" [currentType]=\"currentType\" [types]=\"types\" [detailUrl]=\"detailUrl\"\n [showSearchInput]=\"showSearchInput\" [q]=\"q\" [page]=\"page\" [size]=\"size\" [sort]=\"sort\"\n (parametersChanged)=\"updateUrl($event)\"/>\n", dependencies: [{ kind: "component", type: RecordSearchComponent, selector: "ng-core-record-search", inputs: ["currentType", "q", "page", "size", "sort", "adminMode", "detailUrl", "types", "showSearchInput", "showLabel"], outputs: ["parametersChanged", "recordsSearched"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordSearchPageComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-record-search-page', standalone: false, template: "<!--\n RERO angular core\n Copyright (C) 2020 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<ng-core-record-search class=\"core:my-1\" [adminMode]=\"adminMode\" [currentType]=\"currentType\" [types]=\"types\" [detailUrl]=\"detailUrl\"\n [showSearchInput]=\"showSearchInput\" [q]=\"q\" [page]=\"page\" [size]=\"size\" [sort]=\"sort\"\n (parametersChanged)=\"updateUrl($event)\"/>\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const typeResolver = (route) => {
return route.params["type"];
};
const routes = [
{
path: ':type',
title: typeResolver,
component: RecordSearchPageComponent
},
{
path: ':type/detail/:pid',
title: typeResolver,
component: DetailComponent
},
{
path: ':type/edit/:pid',
component: EditorComponent
},
{
path: ':type/new',
title: typeResolver,
component: EditorComponent
}
];
class RecordRoutingModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordRoutingModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: RecordRoutingModule, imports: [i2$8.RouterModule], exports: [RouterModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordRoutingModule, imports: [RouterModule.forChild(routes), RouterModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordRoutingModule, decorators: [{
type: NgModule,
args: [{
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2024-2025 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;
class SearchAutocompleteComponent {
constructor() {
this.recordService = inject(RecordService);
this.sanitizePipe = inject(SanitizePipe);
// Input
this.delay = input(300);
this.groupClass = input('core:text-gray-400');
this.inputStyleClass = input('core:w-full');
this.minLength = input(3);
this.placeholder = input(null);
this.recordTypes = input.required();
this.scrollHeight = input(CONFIG.DEFAULT_SELECT_SCROLL_HEIGHT);
this.styleClass = input('core:w-full');
this.value = input();
// Output
this.onSearch = output();
// Current value
this.currentValue = '';
// Whether to display options as grouped when nested options are provided.
this.group = false;
this.overlayOptions = {};
// User Query
this.query = new Subject();
this.suggestions = toSignal(this.query.pipe(switchMap$1((query) => this.getSuggestions(query))), { initialValue: [] });
}
buttonClick(event) {
this.onSearch.emit(this.currentValue);
}
setSuggestionQuery(event) {
this.query.next(event.query);
}
onSelectValue(event) {
this.onSearch.emit(event.value);
}
search(event) {
if (event?.key === 'Enter') {
this.onSearch.emit(this.currentValue);
}
}
calculateWidth() {
let width = 200;
if (this.autoComplete) {
width = DomHandler.getOuterWidth(this.autoComplete.el.nativeElement);
}
this.overlayOptions = {
contentStyle: { width: width * 1 + 'px' }
};
}
onResize() {
this.calculateWidth();
}
ngAfterViewInit() {
this.calculateWidth();
}
sanitize(value) {
return this.sanitizePipe.transform(value);
}
getSuggestions(query) {
return combineLatest(this.prepareQuery(query)).pipe(map$5((data) => {
if (data.length > 1) {
this.group = true;
const suggestions = [];
data.forEach((hits) => {
if (hits.length > 0) {
const selectedRecordType = this.recordTypes().find((type) => type.index === hits[0].index);
if (!('groupLabel' in selectedRecordType)) {
throw new Error('Missing groupLabel definition');
}
suggestions.push({
label: selectedRecordType.groupLabel,
items: hits
});
}
});
return suggestions;
}
else {
return data[0];
}
}));
}
prepareQuery(query) {
const suggestions = [];
this.recordTypes().forEach((recordType) => {
const queryParams = [];
if ('queryParams' in recordType) {
Object.keys(recordType.queryParams).forEach(key => {
queryParams.push(`${key}:${recordType.queryParams[key].replace(':', '\\:')}`);
});
}
let queryRecord = `${recordType.field}:${query}`;
if (queryParams.length > 0) {
queryRecord += ` AND ${queryParams.join(' AND ')}`;
}
suggestions.push(this.recordService.getRecords(recordType.index, queryRecord, 1, recordType.maxSuggestions || 10, [], recordType.preFilters || {}, null, recordType.sort || null).pipe(map$5((data) => recordType.processSuggestions(data, query))));
});
return suggestions;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: SearchAutocompleteComponent, isStandalone: false, selector: "ng-core-search-autocomplete", inputs: { delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, groupClass: { classPropertyName: "groupClass", publicName: "groupClass", isSignal: true, isRequired: false, transformFunction: null }, inputStyleClass: { classPropertyName: "inputStyleClass", publicName: "inputStyleClass", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, recordTypes: { classPropertyName: "recordTypes", publicName: "recordTypes", isSignal: true, isRequired: true, transformFunction: null }, scrollHeight: { classPropertyName: "scrollHeight", publicName: "scrollHeight", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSearch: "onSearch" }, host: { listeners: { "window:resize": "onResize()" } }, viewQueries: [{ propertyName: "autoComplete", first: true, predicate: ["autoComplete"], descendants: true }], ngImport: i0, template: `
<p-autoComplete
#autoComplete
[overlayOptions]="overlayOptions"
dropdownIcon="fa fa-search"
[dropdown]="true"
dropdownMode="none"
[styleClass]="styleClass()"
[inputStyleClass]="inputStyleClass()"
[delay]="delay()"
[group]="group"
[minLength]="minLength()"
[placeholder]="placeholder()"
[ngModel]="value()"
(ngModelChange)="currentValue = sanitize($event)"
[scrollHeight]="scrollHeight()"
[suggestions]="suggestions()"
(completeMethod)="setSuggestionQuery($event)"
(onSelect)="onSelectValue($event)"
(onKeyUp)="search($event)"
(onDropdownClick)="buttonClick($event)"
[pAutoFocus]="true"
>
<ng-template let-item #group>
<div class="core:flex core:items-center" [ngClass]="groupClass()" [innerHTML]="item.label"></div>
</ng-template>
<ng-template let-item #item>
@if(item.iconClass) {
<i [ngClass]="item.iconClass"></i>
}
<span [innerHTML]="item.label" [title]="item.originalLabel ? item.originalLabel : ''"></span>
</ng-template>
</p-autoComplete>
`, isInline: true, dependencies: [{ kind: "component", type: i1$3.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "delay", "style", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "disabled", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "maxlength", "name", "required", "size", "appendTo", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "field", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "itemSize", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "variant", "fluid"], outputs: ["completeMethod", "onSelect", "onUnselect", "onFocus", "onBlur", "onDropdownClick", "onClear", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "directive", type: i1$1.AutoFocus, selector: "[pAutoFocus]", inputs: ["autofocus", "pAutoFocus"] }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchAutocompleteComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-core-search-autocomplete',
template: `
<p-autoComplete
#autoComplete
[overlayOptions]="overlayOptions"
dropdownIcon="fa fa-search"
[dropdown]="true"
dropdownMode="none"
[styleClass]="styleClass()"
[inputStyleClass]="inputStyleClass()"
[delay]="delay()"
[group]="group"
[minLength]="minLength()"
[placeholder]="placeholder()"
[ngModel]="value()"
(ngModelChange)="currentValue = sanitize($event)"
[scrollHeight]="scrollHeight()"
[suggestions]="suggestions()"
(completeMethod)="setSuggestionQuery($event)"
(onSelect)="onSelectValue($event)"
(onKeyUp)="search($event)"
(onDropdownClick)="buttonClick($event)"
[pAutoFocus]="true"
>
<ng-template let-item #group>
<div class="core:flex core:items-center" [ngClass]="groupClass()" [innerHTML]="item.label"></div>
</ng-template>
<ng-template let-item #item>
@if(item.iconClass) {
<i [ngClass]="item.iconClass"></i>
}
<span [innerHTML]="item.label" [title]="item.originalLabel ? item.originalLabel : ''"></span>
</ng-template>
</p-autoComplete>
`,
standalone: false
}]
}], propDecorators: { autoComplete: [{
type: ViewChild,
args: ['autoComplete']
}], onResize: [{
type: HostListener,
args: ['window:resize']
}] } });
/*
* RERO angular core
* Copyright (C) 2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
;
class SearchFieldsComponent {
constructor() {
this.translate = inject(TranslateService);
// Input
this.searchLabel = input('Search in');
this.resetLabel = input('Reset');
this.searchFields = input.required();
this.withResetAction = input(true);
// Output
this.onChange = output();
// Field label
this.label = signal(this.processLabel());
// Field items
this.items = [];
}
/** onInit hook */
ngOnInit() {
this.searchFields().map((field) => {
this.items.push({
label: field.label,
command: () => this.select(field),
});
});
if (this.withResetAction()) {
this.items.push({ separator: true });
this.items.push({
label: this.translate.instant(this.resetLabel()),
command: () => this.reset(),
});
}
this.label.set(this.processLabel());
}
/**
* Select a field from the menu
* @param field - SearchField
*/
select(field) {
this.label.set(this.processLabel(field.label));
this.onChange.emit({ action: 'select', field: field });
}
/** Reset search field */
reset() {
this.label.set(this.processLabel());
this.onChange.emit({ action: 'reset' });
}
/**
* Label formatting
* @param label - The name of the field
* @returns string, The formatted label
*/
processLabel(label = '…') {
return this.translate.instant(this.searchLabel()) + ' ' + label;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchFieldsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.14", type: SearchFieldsComponent, isStandalone: false, selector: "ng-core-search-fields", inputs: { searchLabel: { classPropertyName: "searchLabel", publicName: "searchLabel", isSignal: true, isRequired: false, transformFunction: null }, resetLabel: { classPropertyName: "resetLabel", publicName: "resetLabel", isSignal: true, isRequired: false, transformFunction: null }, searchFields: { classPropertyName: "searchFields", publicName: "searchFields", isSignal: true, isRequired: true, transformFunction: null }, withResetAction: { classPropertyName: "withResetAction", publicName: "withResetAction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onChange: "onChange" }, ngImport: i0, template: "\n<!--\n RERO angular core\n Copyright (C) 2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-splitButton [label]=\"label()\" [model]=\"items\" outlined />\n", dependencies: [{ kind: "component", type: i3$3.SplitButton, selector: "p-splitbutton, p-splitButton, p-split-button", inputs: ["model", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "icon", "iconPos", "label", "tooltip", "tooltipOptions", "style", "styleClass", "menuStyle", "menuStyleClass", "dropdownIcon", "appendTo", "dir", "expandAriaLabel", "showTransitionOptions", "hideTransitionOptions", "buttonProps", "menuButtonProps", "autofocus", "disabled", "tabindex", "menuButtonDisabled", "buttonDisabled"], outputs: ["onClick", "onMenuHide", "onMenuShow", "onDropdownClick"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SearchFieldsComponent, decorators: [{
type: Component,
args: [{ selector: 'ng-core-search-fields', standalone: false, template: "\n<!--\n RERO angular core\n Copyright (C) 2024 RERO\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU Affero General Public License as published by\n the Free Software Foundation, version 3 of the License.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Affero General Public License for more details.\n\n You should have received a copy of the GNU Affero General Public License\n along with this program. If not, see <http://www.gnu.org/licenses/>.\n-->\n<p-splitButton [label]=\"label()\" [model]=\"items\" outlined />\n" }]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class RecordModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: RecordModule, declarations: [RecordSearchPageComponent,
RecordSearchComponent,
RecordSearchResultComponent,
RecordSearchAggregationComponent,
JsonComponent,
DetailComponent,
RecordDetailDirective,
JsonComponent$1,
GetRecordPipe,
EditorComponent,
AddFieldEditorComponent,
DropdownLabelEditorComponent,
ArrayTypeComponent,
ObjectTypeComponent,
SwitchComponent,
MultiSchemaTypeComponent,
BucketsComponent,
FormFieldWrapperComponent,
HideWrapperComponent,
AggregationSliderComponent,
LoadTemplateFormComponent,
SaveTemplateFormComponent,
CardWrapperComponent,
LabelComponent,
TextareaFieldComponent,
MarkdownFieldComponent,
AggregationDateRangeComponent,
ExportButtonComponent,
ListFiltersComponent,
BucketNamePipe,
PasswordGeneratorTypeComponent,
DetailButtonComponent,
RadioButtonComponent,
PaginatorComponent,
MenuSortComponent,
SearchFiltersComponent,
SearchFieldsComponent,
SearchTabsComponent,
SearchAutocompleteComponent], imports: [
// NOTE : BrowserAnimationModule **should** be include in application core module.
// BrowserAnimationsModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
RecordRoutingModule,
DatePickerModule,
DataViewModule,
ClipboardModule,
RadioButtonModule,
AutoCompleteModule,
FormlySelectModule, i4$1.FormlyModule, FormlyPrimeNGModule,
NgCoreFormlyDatePickerModule,
NgCoreFormlyInputModule,
NgCoreFormlyMultiCheckboxModule,
NgCoreFormlyMultiSelectModule,
NgCoreFormlyRemoteAutocompleteModule,
NgCoreFormlySelectModule,
NgCoreFormlyTreeSelectModule], exports: [RecordSearchComponent,
GetRecordPipe,
CoreModule,
EditorComponent,
FormlyModule,
FormlyPrimeNGModule,
FormlySelectModule,
ExportButtonComponent,
DetailButtonComponent,
RecordDetailDirective,
ListFiltersComponent,
RecordSearchAggregationComponent,
SearchFiltersComponent,
SearchFieldsComponent,
SearchAutocompleteComponent,
NgCoreFormlyDatePickerModule,
NgCoreFormlyInputModule,
NgCoreFormlyMultiCheckboxModule,
NgCoreFormlyRemoteAutocompleteModule,
NgCoreFormlySelectModule,
MenuSortComponent,
NgCoreFormlyTreeSelectModule,
SearchTabsComponent,
PaginatorComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordModule, providers: [
{
provide: FORMLY_CONFIG,
multi: true,
useFactory: registerNgCoreFormlyExtension,
deps: [TranslateService, RecordService]
}
], imports: [
// NOTE : BrowserAnimationModule **should** be include in application core module.
// BrowserAnimationsModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
RecordRoutingModule,
DatePickerModule,
DataViewModule,
ClipboardModule,
RadioButtonModule,
AutoCompleteModule,
FormlySelectModule,
FormlyModule.forRoot({
extensions: [
{ name: 'email', extension: { prePopulate: emailValidator } }
],
extras: {
checkExpressionOn: 'changeDetectionCheck',
resetFieldOnHide: true
},
types: [
{ name: 'input', component: NgCoreFormlyFieldInput },
{ name: 'boolean', component: SwitchComponent },
{ name: 'enum', extends: 'select' },
{ name: 'array', component: ArrayTypeComponent, wrappers: ['card'] },
{ name: 'object', component: ObjectTypeComponent, wrappers: ['card'] },
{ name: 'multischema', component: MultiSchemaTypeComponent },
{ name: 'textarea', component: TextareaFieldComponent },
{ name: 'markdown', component: MarkdownFieldComponent },
{ name: 'passwordGenerator', component: PasswordGeneratorTypeComponent },
{ name: 'radioButton', component: RadioButtonComponent },
],
wrappers: [
{ name: 'form-field', component: FormFieldWrapperComponent },
{ name: 'hide', component: HideWrapperComponent },
{ name: 'card', component: CardWrapperComponent }
]
}),
FormlyPrimeNGModule,
NgCoreFormlyDatePickerModule,
NgCoreFormlyInputModule,
NgCoreFormlyMultiCheckboxModule,
NgCoreFormlyMultiSelectModule,
NgCoreFormlyRemoteAutocompleteModule,
NgCoreFormlySelectModule,
NgCoreFormlyTreeSelectModule, CoreModule,
FormlyModule,
FormlyPrimeNGModule,
FormlySelectModule,
NgCoreFormlyDatePickerModule,
NgCoreFormlyInputModule,
NgCoreFormlyMultiCheckboxModule,
NgCoreFormlyRemoteAutocompleteModule,
NgCoreFormlySelectModule,
NgCoreFormlyTreeSelectModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: RecordModule, decorators: [{
type: NgModule,
args: [{
declarations: [
RecordSearchPageComponent,
RecordSearchComponent,
RecordSearchResultComponent,
RecordSearchAggregationComponent,
JsonComponent,
DetailComponent,
RecordDetailDirective,
JsonComponent$1,
GetRecordPipe,
EditorComponent,
AddFieldEditorComponent,
DropdownLabelEditorComponent,
ArrayTypeComponent,
ObjectTypeComponent,
SwitchComponent,
MultiSchemaTypeComponent,
BucketsComponent,
FormFieldWrapperComponent,
HideWrapperComponent,
AggregationSliderComponent,
LoadTemplateFormComponent,
SaveTemplateFormComponent,
CardWrapperComponent,
LabelComponent,
TextareaFieldComponent,
MarkdownFieldComponent,
AggregationDateRangeComponent,
ExportButtonComponent,
ListFiltersComponent,
BucketNamePipe,
PasswordGeneratorTypeComponent,
DetailButtonComponent,
RadioButtonComponent,
PaginatorComponent,
MenuSortComponent,
SearchFiltersComponent,
SearchFieldsComponent,
SearchTabsComponent,
SearchAutocompleteComponent
],
imports: [
// NOTE : BrowserAnimationModule **should** be include in application core module.
// BrowserAnimationsModule,
CoreModule,
FormsModule,
ReactiveFormsModule,
RecordRoutingModule,
DatePickerModule,
DataViewModule,
ClipboardModule,
RadioButtonModule,
AutoCompleteModule,
FormlySelectModule,
FormlyModule.forRoot({
extensions: [
{ name: 'email', extension: { prePopulate: emailValidator } }
],
extras: {
checkExpressionOn: 'changeDetectionCheck',
resetFieldOnHide: true
},
types: [
{ name: 'input', component: NgCoreFormlyFieldInput },
{ name: 'boolean', component: SwitchComponent },
{ name: 'enum', extends: 'select' },
{ name: 'array', component: ArrayTypeComponent, wrappers: ['card'] },
{ name: 'object', component: ObjectTypeComponent, wrappers: ['card'] },
{ name: 'multischema', component: MultiSchemaTypeComponent },
{ name: 'textarea', component: TextareaFieldComponent },
{ name: 'markdown', component: MarkdownFieldComponent },
{ name: 'passwordGenerator', component: PasswordGeneratorTypeComponent },
{ name: 'radioButton', component: RadioButtonComponent },
],
wrappers: [
{ name: 'form-field', component: FormFieldWrapperComponent },
{ name: 'hide', component: HideWrapperComponent },
{ name: 'card', component: CardWrapperComponent }
]
}),
FormlyPrimeNGModule,
NgCoreFormlyDatePickerModule,
NgCoreFormlyInputModule,
NgCoreFormlyMultiCheckboxModule,
NgCoreFormlyMultiSelectModule,
NgCoreFormlyRemoteAutocompleteModule,
NgCoreFormlySelectModule,
NgCoreFormlyTreeSelectModule,
],
exports: [
RecordSearchComponent,
GetRecordPipe,
CoreModule,
EditorComponent,
FormlyModule,
FormlyPrimeNGModule,
FormlySelectModule,
ExportButtonComponent,
DetailButtonComponent,
RecordDetailDirective,
ListFiltersComponent,
RecordSearchAggregationComponent,
SearchFiltersComponent,
SearchFieldsComponent,
SearchAutocompleteComponent,
NgCoreFormlyDatePickerModule,
NgCoreFormlyInputModule,
NgCoreFormlyMultiCheckboxModule,
NgCoreFormlyRemoteAutocompleteModule,
NgCoreFormlySelectModule,
MenuSortComponent,
NgCoreFormlyTreeSelectModule,
SearchTabsComponent,
PaginatorComponent
],
providers: [
{
provide: FORMLY_CONFIG,
multi: true,
useFactory: registerNgCoreFormlyExtension,
deps: [TranslateService, RecordService]
}
]
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class CryptoJsService {
constructor() {
this.coreConfigService = inject(CoreConfigService);
}
encrypt(value) {
const _key = this.secretPassphrase();
return CryptoJS.AES.encrypt(value, _key, { iv: _key });
}
decrypt(value) {
const _key = this.secretPassphrase();
return CryptoJS.AES.decrypt(value, _key, { iv: _key }).toString(CryptoJS.enc.Utf8);
}
secretPassphrase() {
return CryptoJS.enc.Utf8.parse(this.coreConfigService.secretPassphrase);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CryptoJsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CryptoJsService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CryptoJsService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Service for managing data saved into local storage.
*/
class LocalStorageService {
constructor() {
this.cryptoService = inject(CryptoJsService);
/**
* Event for set data on local storage
*/
this.onSet = new Subject();
/**
* Event for remove data on local storage
*/
this.onRemove = new Subject();
/**
* Event for clear on local storage
*/
this.onClear = new Subject();
}
/**
* On set observable
* @return onSet subject observable
*/
get onSet$() {
return this.onSet.asObservable();
}
/**
* On remove observable
* @return onRemove subject observable
*/
get onRemove$() {
return this.onRemove.asObservable();
}
/**
* On clear observable
* @return onClear subject observable
*/
get onClear$() {
return this.onClear.asObservable();
}
/**
* Set a new key on LocalStorage
* @param key - string
* @param value - string
* @return LocalStorageService
*/
set(key, value) {
const data = { date: new Date(), data: value };
localStorage.setItem(key, this.cryptoService.encrypt(JSON.stringify(data)));
this.onSet.next({ key, data });
return this;
}
/**
* Update date of current object qualified by the key
* @param key - string
* @return LocalStorageService;
*/
updateDate(key) {
const local = this.getItem(key);
local.date = new Date();
localStorage.setItem(key, this.cryptoService.encrypt(JSON.stringify(local)));
return this;
}
/**
* Return the status of current object qualified by the key
* @param key - string
* @param seconds - number of seconds session expired
* @return boolean
*/
isExpired(key, seconds) {
const local = this.getItem(key);
const today = new Date();
const localDate = new Date(local.date);
localDate.setSeconds(localDate.getSeconds() + seconds);
return today > localDate;
}
/**
* Return JSON content of the LocaleStorage
* @param key - string
* @param field - string (default: data)
* @return JSON
*/
get(key, field = 'data') {
const local = this.getItem(key);
return local ? local[field] : null;
}
/**
* Remove a key of LocaleStorage
* @param key - string
* @return LocalStorageService
*/
remove(key) {
localStorage.removeItem(key);
this.onRemove.next(null);
return this;
}
/**
* Clear LocaleStorage
* @return LocalStorageService
*/
clear() {
localStorage.clear();
this.onClear.next(null);
return this;
}
/**
* has key in LocaleStorage
* @param key - string
* @return Boolean
*/
has(key) {
return this.keys().indexOf(key) > -1;
}
/**
* Get local storage with the key
* @param key - string
*/
getItem(key) {
return JSON.parse(this.cryptoService.decrypt(localStorage.getItem(key)));
}
/**
* Return keys of LocaleStorage
* @return array
*/
keys() {
return Object.keys(localStorage);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LocalStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LocalStorageService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: LocalStorageService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
// @dynamic
class TimeValidator {
static { this.FORMAT = 'hh:mm'; }
/**
* Allow to control if time interval limits are well formed : the end limit should be 'older' the start limit
* @param start: the field name where to find the start limit value
* @param end: the field name where to find the end limit value
*/
static greaterThanValidator(start, end) {
return (control) => {
if (control) {
let isLessThan = false;
const startTime = control.get(start);
const endTime = control.get(end);
const startDate = DateTime.fromFormat(startTime.value, TimeValidator.FORMAT);
const endDate = DateTime.fromFormat(endTime.value, TimeValidator.FORMAT);
if (startDate.toFormat(TimeValidator.FORMAT) !== '00:00' || endDate.toFormat(TimeValidator.FORMAT) !== '00:00') {
isLessThan = startDate.diff(endDate) >= 0;
}
return (isLessThan)
? ({ lessThan: { value: isLessThan } })
: null;
}
};
}
static RangePeriodValidator() {
return (control) => {
if (control.value) {
let isRangeLessThan = false;
const times = control.get('times');
if (control.get('is_open').value && times.value.length > 1) {
const firstStartDate = DateTime.fromFormat(times.at(0).get('start_time').value, TimeValidator.FORMAT);
const firstEndDate = DateTime.fromFormat(times.at(0).get('end_time').value, TimeValidator.FORMAT);
const lastStartDate = DateTime.fromFormat(times.at(1).get('start_time').value, TimeValidator.FORMAT);
const lastEndDate = DateTime.fromFormat(times.at(1).get('end_time').value, TimeValidator.FORMAT);
if (firstStartDate > lastStartDate) {
isRangeLessThan = firstStartDate.diff(lastStartDate) <= 0
|| firstStartDate.diff(lastEndDate) <= 0;
}
else {
isRangeLessThan = lastStartDate.diff(firstEndDate) <= 0
|| lastStartDate.diff(firstEndDate) <= 0;
}
}
return (isRangeLessThan)
? ({ rangeLessThan: { value: isRangeLessThan } })
: null;
}
};
}
}
// @dynamic
class UniqueValidator {
static createValidator(recordService, recordType, fieldName, excludePid = null) {
return (control) => {
return recordService.valueAlreadyExists(recordType, fieldName, control.value, excludePid);
};
}
}
/*
* RERO angular core
* Copyright (C) 2020-2024 RERO
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Generated bundle index. Do not edit.
*/
export { AbstractCanDeactivateComponent, AddFieldEditorComponent, ApiService, ArrayTypeComponent, BucketNameService, CONFIG, CallbackArrayFilterPipe, ComponentCanDeactivateGuard, CoreConfigService, CoreModule, CryptoJsService, DatePickerComponent, DateTranslatePipe, DefaultPipe, DetailButtonComponent, DetailComponent, DialogComponent, DropdownLabelEditorComponent, EditorComponent, ErrorComponent, ExportButtonComponent, FilesizePipe, FormOptionsProcessExtension, GetRecordPipe, JSONSchemaService, ListFiltersComponent, LocalStorageService, LoggerService, MarkdownPipe, MenuSortComponent, MultiCheckboxComponent, MultiSchemaTypeComponent, MultiSelectComponent, NgCoreFormlyDatePickerModule, NgCoreFormlyExtension, NgCoreFormlyFieldInput, NgCoreFormlyInputModule, NgCoreFormlyMultiCheckboxModule, NgCoreFormlyMultiSelectModule, NgCoreFormlyRemoteAutocompleteModule, NgCoreFormlySelectModule, NgCoreFormlyTreeSelectModule, NgCoreTranslateService, Nl2brPipe, ObjectTypeComponent, PaginatorComponent, PasswordGeneratorTypeComponent, PrimeNgCoreModule, Record, RecordDetailDirective, RecordHandleErrorService, RecordModule, RecordSearchAggregationComponent, RecordSearchComponent, RecordSearchPageComponent, RecordSearchService, RecordService, RecordUiService, RemoteAutocomplete, RemoteAutocompleteService, RouteCollectionService, SanitizePipe, SearchAutocompleteComponent, SearchFieldsComponent, SearchFiltersComponent, SearchInputComponent, SearchResult, SearchTabsComponent, SelectComponent, SortByKeysPipe, SwitchComponent, TextReadMoreComponent, TimeValidator, TranslateExtension, TranslateLabelService, TranslateLanguagePipe, TranslateLanguageService, TranslateLoader, TreeSelectComponent, TruncateTextPipe, UniqueValidator, UpperCaseFirstPipe, Validators, capitalize, cleanDictKeys, datesGreaterThanValidator, extractIdOnRef, isEmpty, primeNGConfig, processJsonSchema, registerNgCoreFormlyExtension, removeEmptyValues, resolve$ref, resolveRefs, sortByKeys };
//# sourceMappingURL=rero-ng-core.mjs.map