@sinequa/atomic-angular 1.19.2 → 1.19.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/sinequa-atomic-angular.mjs +311 -230
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +74 -12
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import highlightWords from 'highlight-words';
|
|
|
7
7
|
import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
|
|
8
8
|
import { withDevtools } from '@angular-architects/ngrx-toolkit';
|
|
9
9
|
import { signalStore, signalStoreFeature, withState, withMethods, patchState, getState, withComputed } from '@ngrx/signals';
|
|
10
|
-
import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, isRedirectPending, info, clearSessionTokens, login, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, fetchQuery, flattenLegacyFilterValues, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest,
|
|
10
|
+
import { EngineType, fetchApp, extraColumns, sysLang, fetchPrincipal, globalConfig, filter, getQueryParamsFromUrl, isRedirectPending, info, clearSessionTokens, login, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, fetchQuery, flattenLegacyFilterValues, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, Audit, isObject, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
|
|
11
11
|
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
12
12
|
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
|
|
13
13
|
import { HttpClient, HttpParams, httpResource, HttpResponse, HttpContextToken, HttpHeaders } from '@angular/common/http';
|
|
@@ -3262,7 +3262,12 @@ class QueryService {
|
|
|
3262
3262
|
};
|
|
3263
3263
|
return r;
|
|
3264
3264
|
}), tap(response => {
|
|
3265
|
-
|
|
3265
|
+
// An "open" query (expanding a tree aggregation node) only fetches the node's
|
|
3266
|
+
// children: its response is not the page's search result (no tabs, no records...).
|
|
3267
|
+
// Publishing it would wipe the state derived from `result` (e.g. the tab counts).
|
|
3268
|
+
if (query.action !== "open") {
|
|
3269
|
+
this.result.set(response);
|
|
3270
|
+
}
|
|
3266
3271
|
info("queryService.getResults success - data: ", response);
|
|
3267
3272
|
}));
|
|
3268
3273
|
}
|
|
@@ -4404,116 +4409,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
4404
4409
|
}]
|
|
4405
4410
|
}] });
|
|
4406
4411
|
|
|
4407
|
-
class JsonMethodPluginService {
|
|
4408
|
-
http = inject(HttpClient);
|
|
4409
|
-
API_URL = `${globalConfig.backendUrl}/api/v1`;
|
|
4410
|
-
/**
|
|
4411
|
-
* Call a JsonMethod plugin using an HTTP POST
|
|
4412
|
-
*
|
|
4413
|
-
* @param method The name of the JsonMethod plugin
|
|
4414
|
-
* @param query Parameters to pass to the plugin
|
|
4415
|
-
* @param options HTTP options for the request
|
|
4416
|
-
* @returns An observable of the plugin's return value
|
|
4417
|
-
*/
|
|
4418
|
-
post(method, query, options) {
|
|
4419
|
-
if (!isObject(query)) {
|
|
4420
|
-
return throwError(() => ({ error: 'invalid query object' }));
|
|
4421
|
-
}
|
|
4422
|
-
return this.http.post(`${this.API_URL}/plugin/${method}`, query, options);
|
|
4423
|
-
}
|
|
4424
|
-
/**
|
|
4425
|
-
* Call a JsonMethod plugin using an HTTP GET
|
|
4426
|
-
*
|
|
4427
|
-
* @param method The name of the JsonMethod plugin
|
|
4428
|
-
* @param query Parameters to pass to the plugin
|
|
4429
|
-
* @param options HTTP options for the request
|
|
4430
|
-
* @returns An observable of the plugin's return value
|
|
4431
|
-
*/
|
|
4432
|
-
get(method, query, options) {
|
|
4433
|
-
return this.http.get(`${this.API_URL}/plugin/${method}`, {
|
|
4434
|
-
params: query,
|
|
4435
|
-
...options
|
|
4436
|
-
});
|
|
4437
|
-
}
|
|
4438
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: JsonMethodPluginService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4439
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: JsonMethodPluginService, providedIn: 'root' });
|
|
4440
|
-
}
|
|
4441
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: JsonMethodPluginService, decorators: [{
|
|
4442
|
-
type: Injectable,
|
|
4443
|
-
args: [{
|
|
4444
|
-
providedIn: 'root'
|
|
4445
|
-
}]
|
|
4446
|
-
}] });
|
|
4447
|
-
|
|
4448
|
-
/**
|
|
4449
|
-
* The route an audit event names: the path, without its leading slash, query string or fragment.
|
|
4450
|
-
* `/search?q=irsn#p2` and `/search` are the same route — arriving on one from the other is a new
|
|
4451
|
-
* query, not a navigation.
|
|
4452
|
-
*/
|
|
4453
|
-
function routeNameOf(url) {
|
|
4454
|
-
return url.slice(1).split(/[?#]/)[0];
|
|
4455
|
-
}
|
|
4456
|
-
class NavigationService {
|
|
4457
|
-
router = inject(Router);
|
|
4458
|
-
auditService = inject(AuditService);
|
|
4459
|
-
// The route of the previous navigation, used only to dedupe audit route-change notifications in
|
|
4460
|
-
// navigationEnd$ below. Kept separate from urlAfterNavigation so that getter stays correct
|
|
4461
|
-
// regardless of whether anything subscribes to navigationEnd$.
|
|
4462
|
-
//
|
|
4463
|
-
// It used to hold the full url (`/search?q=irsn`) while the comparison was against the bare route
|
|
4464
|
-
// name (`search`): the two could never be equal, so the dedupe never fired and every navigation
|
|
4465
|
-
// was audited — including the query-param changes that make up most of a search session.
|
|
4466
|
-
previousRouteName = null;
|
|
4467
|
-
/**
|
|
4468
|
-
* The URL of the current navigation, used to redirect the user after login.
|
|
4469
|
-
*
|
|
4470
|
-
* Sourced directly from `Router.url`, which Angular keeps up to date on every navigation with no
|
|
4471
|
-
* subscription required — unlike a field updated as a side effect of `navigationEnd$` below,
|
|
4472
|
-
* which only happens for whoever actually subscribes to it.
|
|
4473
|
-
*
|
|
4474
|
-
* The authentication screens are never returned: sending someone back to `/logout` after they
|
|
4475
|
-
* signed in defeats the sign-in, and back to `/login` loops. The exclusion used to be a literal
|
|
4476
|
-
* `!== "/login"`, which let through `/logout`, `/login?mode=changepassword` — same screen, one
|
|
4477
|
-
* query param — and any application whose `loginPath` is not spelled `/login`.
|
|
4478
|
-
*/
|
|
4479
|
-
get urlAfterNavigation() {
|
|
4480
|
-
const url = this.router.url;
|
|
4481
|
-
return isAuthRoute(url, globalConfig.loginPath) ? null : url;
|
|
4482
|
-
}
|
|
4483
|
-
/**
|
|
4484
|
-
* Observable that emits events of type `NavigationEnd` from the Angular Router.
|
|
4485
|
-
*
|
|
4486
|
-
* This observable performs the following operations:
|
|
4487
|
-
* - Maps all router events to `RouterEvent`.
|
|
4488
|
-
* - Filters the events to only include instances of `NavigationEnd`.
|
|
4489
|
-
* - Taps into the event stream to notify the audit service when the route itself changes — a new
|
|
4490
|
-
* query, a filter or another page of results stays on the same route and is not one.
|
|
4491
|
-
* - Shares the replayed value with a buffer size of 1 to ensure subscribers receive the latest emitted value.
|
|
4492
|
-
*
|
|
4493
|
-
* @type Observable<RouterEvent>
|
|
4494
|
-
*/
|
|
4495
|
-
navigationEnd$ = this.router.events.pipe(map(event => event), filter$1((event) => event instanceof NavigationEnd), tap(event => {
|
|
4496
|
-
const routeName = routeNameOf(event.url);
|
|
4497
|
-
// The empty name is the application root, which has never been audited.
|
|
4498
|
-
if (routeName && routeName !== this.previousRouteName) {
|
|
4499
|
-
this.auditService.notifyRouteChange(routeName);
|
|
4500
|
-
}
|
|
4501
|
-
// Every route is remembered, the authentication screens included. Skipping them would swallow
|
|
4502
|
-
// the return: leaving `/search` for `/login` and coming back is two route changes, and the
|
|
4503
|
-
// second one would compare against a `search` the user has meanwhile left. (The exclusion that
|
|
4504
|
-
// used to sit here never ran — the dedupe above it could not fire.)
|
|
4505
|
-
this.previousRouteName = routeName;
|
|
4506
|
-
}), shareReplay(1));
|
|
4507
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4508
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NavigationService, providedIn: "root" });
|
|
4509
|
-
}
|
|
4510
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NavigationService, decorators: [{
|
|
4511
|
-
type: Injectable,
|
|
4512
|
-
args: [{
|
|
4513
|
-
providedIn: "root"
|
|
4514
|
-
}]
|
|
4515
|
-
}] });
|
|
4516
|
-
|
|
4517
4412
|
// pre-configured highlights's colors
|
|
4518
4413
|
const HIGHLIGHTS = new InjectionToken('highlights', {
|
|
4519
4414
|
factory: () => [
|
|
@@ -4799,17 +4694,45 @@ class PreviewService {
|
|
|
4799
4694
|
* @returns the html id
|
|
4800
4695
|
*/
|
|
4801
4696
|
getEntityId(entity, value, index) {
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4697
|
+
return this.getEntityLocations(entity, value)[index];
|
|
4698
|
+
}
|
|
4699
|
+
/**
|
|
4700
|
+
* Get the ids of every occurrence of an entity value that is actually highlighted in the preview,
|
|
4701
|
+
* ordered by position in the document.
|
|
4702
|
+
*
|
|
4703
|
+
* This is the source of truth for entity navigation: the `count` carried by the indexed record can be
|
|
4704
|
+
* greater than the number of occurrences located in the converted document (ES-32245).
|
|
4705
|
+
*
|
|
4706
|
+
* @param entity the entity type (i.e. "geo")
|
|
4707
|
+
* @param value the entity value (i.e. "AUSTRALIA")
|
|
4708
|
+
* @returns the html ids (`positionInCategories[entity]`), empty when nothing is highlighted
|
|
4709
|
+
*/
|
|
4710
|
+
getEntityLocations(entity, value) {
|
|
4711
|
+
const data = this.previewData;
|
|
4712
|
+
if (!data)
|
|
4713
|
+
return [];
|
|
4714
|
+
let perEntity = this.entityLocationsCache.get(data);
|
|
4715
|
+
if (!perEntity) {
|
|
4716
|
+
perEntity = new Map();
|
|
4717
|
+
this.entityLocationsCache.set(data, perEntity);
|
|
4718
|
+
}
|
|
4719
|
+
const key = `${entity}\u0000${value}`;
|
|
4720
|
+
let ids = perEntity.get(key);
|
|
4721
|
+
if (!ids) {
|
|
4722
|
+
ids = [];
|
|
4723
|
+
for (const item of data.highlightsPerLocation) {
|
|
4724
|
+
// NB: positionInCategories[entity] is a zero-based index, so the first occurrence is 0.
|
|
4725
|
+
// Use a presence check (not truthiness) to avoid dropping that first occurrence.
|
|
4726
|
+
const position = item.positionInCategories[entity];
|
|
4727
|
+
if (position !== undefined && item.values.some(v => v === value))
|
|
4728
|
+
ids.push(position);
|
|
4729
|
+
}
|
|
4730
|
+
perEntity.set(key, ids);
|
|
4731
|
+
}
|
|
4732
|
+
return ids;
|
|
4812
4733
|
}
|
|
4734
|
+
/** Memoized `getEntityLocations` results, dropped with the `PreviewData` they were computed from. */
|
|
4735
|
+
entityLocationsCache = new WeakMap();
|
|
4813
4736
|
/**
|
|
4814
4737
|
* Send a message to the prewiew iFrame with the required data to retrieve HTML content for a specific highlight category
|
|
4815
4738
|
*
|
|
@@ -4973,6 +4896,171 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
4973
4896
|
}]
|
|
4974
4897
|
}], ctorParameters: () => [{ type: i0.DestroyRef }] });
|
|
4975
4898
|
|
|
4899
|
+
/**
|
|
4900
|
+
* Cursor over the occurrences of an entity value highlighted in the preview.
|
|
4901
|
+
*
|
|
4902
|
+
* The number of occurrences is the one actually located in the preview (`PreviewService.getEntityLocations`),
|
|
4903
|
+
* not the `count` of the indexed record, which can be greater (ES-32245).
|
|
4904
|
+
*/
|
|
4905
|
+
class EntityNavigationService {
|
|
4906
|
+
previewService = inject(PreviewService);
|
|
4907
|
+
lastPreviewData;
|
|
4908
|
+
/** The current position, shared by the entities panels */
|
|
4909
|
+
navigation = signal(undefined, ...(ngDevMode ? [{ debugName: "navigation" }] : []));
|
|
4910
|
+
/** The value hovered in the panel */
|
|
4911
|
+
hovering = signal(undefined, ...(ngDevMode ? [{ debugName: "hovering" }] : []));
|
|
4912
|
+
/** The 1-based position of the hovered value, `0` when it is not the one being navigated */
|
|
4913
|
+
hoverIndex = computed(() => (this.navigation()?.value === this.hovering() ? (this.navigation()?.index ?? 0) : 0), ...(ngDevMode ? [{ debugName: "hoverIndex" }] : []));
|
|
4914
|
+
/**
|
|
4915
|
+
* Number of occurrences of `value` highlighted in the preview.
|
|
4916
|
+
* `fallback` (the indexed count) is only used while no preview is loaded.
|
|
4917
|
+
*/
|
|
4918
|
+
total(entity, value, fallback = 0) {
|
|
4919
|
+
if (!this.previewService.previewData)
|
|
4920
|
+
return fallback;
|
|
4921
|
+
return this.previewService.getEntityLocations(entity, value).length;
|
|
4922
|
+
}
|
|
4923
|
+
navigate(entity, value, direction) {
|
|
4924
|
+
const ids = this.previewService.getEntityLocations(entity, value);
|
|
4925
|
+
const total = ids.length;
|
|
4926
|
+
if (total === 0) {
|
|
4927
|
+
warn(`No highlighted occurrence for entity ${entity}:`, value);
|
|
4928
|
+
notify.warning(`No occurrence of "${value}" can be reached in the document.`);
|
|
4929
|
+
return;
|
|
4930
|
+
}
|
|
4931
|
+
// The cursor belongs to one document: start over when another preview has been loaded
|
|
4932
|
+
if (this.lastPreviewData !== this.previewService.previewData) {
|
|
4933
|
+
this.lastPreviewData = this.previewService.previewData;
|
|
4934
|
+
this.navigation.set(undefined);
|
|
4935
|
+
}
|
|
4936
|
+
const current = this.navigation();
|
|
4937
|
+
// Start from the first (next) or last (prev) occurrence when the value changes
|
|
4938
|
+
const position = current?.value === value ? current.index : direction === "next" ? 0 : 1;
|
|
4939
|
+
const index = ((position - 1 + (direction === "next" ? 1 : -1) + total) % total) + 1;
|
|
4940
|
+
this.navigation.set({ value, index });
|
|
4941
|
+
const id = ids[index - 1];
|
|
4942
|
+
this.previewService.events.set("scrollTo");
|
|
4943
|
+
this.previewService.sendMessage({ action: "select", id: `${entity}_${id}`, usePassageHighlighter: false });
|
|
4944
|
+
debug(`${direction} navigation:`, { id, entity, value, index, total });
|
|
4945
|
+
}
|
|
4946
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: EntityNavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4947
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: EntityNavigationService, providedIn: "root" });
|
|
4948
|
+
}
|
|
4949
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: EntityNavigationService, decorators: [{
|
|
4950
|
+
type: Injectable,
|
|
4951
|
+
args: [{ providedIn: "root" }]
|
|
4952
|
+
}] });
|
|
4953
|
+
|
|
4954
|
+
class JsonMethodPluginService {
|
|
4955
|
+
http = inject(HttpClient);
|
|
4956
|
+
API_URL = `${globalConfig.backendUrl}/api/v1`;
|
|
4957
|
+
/**
|
|
4958
|
+
* Call a JsonMethod plugin using an HTTP POST
|
|
4959
|
+
*
|
|
4960
|
+
* @param method The name of the JsonMethod plugin
|
|
4961
|
+
* @param query Parameters to pass to the plugin
|
|
4962
|
+
* @param options HTTP options for the request
|
|
4963
|
+
* @returns An observable of the plugin's return value
|
|
4964
|
+
*/
|
|
4965
|
+
post(method, query, options) {
|
|
4966
|
+
if (!isObject(query)) {
|
|
4967
|
+
return throwError(() => ({ error: 'invalid query object' }));
|
|
4968
|
+
}
|
|
4969
|
+
return this.http.post(`${this.API_URL}/plugin/${method}`, query, options);
|
|
4970
|
+
}
|
|
4971
|
+
/**
|
|
4972
|
+
* Call a JsonMethod plugin using an HTTP GET
|
|
4973
|
+
*
|
|
4974
|
+
* @param method The name of the JsonMethod plugin
|
|
4975
|
+
* @param query Parameters to pass to the plugin
|
|
4976
|
+
* @param options HTTP options for the request
|
|
4977
|
+
* @returns An observable of the plugin's return value
|
|
4978
|
+
*/
|
|
4979
|
+
get(method, query, options) {
|
|
4980
|
+
return this.http.get(`${this.API_URL}/plugin/${method}`, {
|
|
4981
|
+
params: query,
|
|
4982
|
+
...options
|
|
4983
|
+
});
|
|
4984
|
+
}
|
|
4985
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: JsonMethodPluginService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4986
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: JsonMethodPluginService, providedIn: 'root' });
|
|
4987
|
+
}
|
|
4988
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: JsonMethodPluginService, decorators: [{
|
|
4989
|
+
type: Injectable,
|
|
4990
|
+
args: [{
|
|
4991
|
+
providedIn: 'root'
|
|
4992
|
+
}]
|
|
4993
|
+
}] });
|
|
4994
|
+
|
|
4995
|
+
/**
|
|
4996
|
+
* The route an audit event names: the path, without its leading slash, query string or fragment.
|
|
4997
|
+
* `/search?q=irsn#p2` and `/search` are the same route — arriving on one from the other is a new
|
|
4998
|
+
* query, not a navigation.
|
|
4999
|
+
*/
|
|
5000
|
+
function routeNameOf(url) {
|
|
5001
|
+
return url.slice(1).split(/[?#]/)[0];
|
|
5002
|
+
}
|
|
5003
|
+
class NavigationService {
|
|
5004
|
+
router = inject(Router);
|
|
5005
|
+
auditService = inject(AuditService);
|
|
5006
|
+
// The route of the previous navigation, used only to dedupe audit route-change notifications in
|
|
5007
|
+
// navigationEnd$ below. Kept separate from urlAfterNavigation so that getter stays correct
|
|
5008
|
+
// regardless of whether anything subscribes to navigationEnd$.
|
|
5009
|
+
//
|
|
5010
|
+
// It used to hold the full url (`/search?q=irsn`) while the comparison was against the bare route
|
|
5011
|
+
// name (`search`): the two could never be equal, so the dedupe never fired and every navigation
|
|
5012
|
+
// was audited — including the query-param changes that make up most of a search session.
|
|
5013
|
+
previousRouteName = null;
|
|
5014
|
+
/**
|
|
5015
|
+
* The URL of the current navigation, used to redirect the user after login.
|
|
5016
|
+
*
|
|
5017
|
+
* Sourced directly from `Router.url`, which Angular keeps up to date on every navigation with no
|
|
5018
|
+
* subscription required — unlike a field updated as a side effect of `navigationEnd$` below,
|
|
5019
|
+
* which only happens for whoever actually subscribes to it.
|
|
5020
|
+
*
|
|
5021
|
+
* The authentication screens are never returned: sending someone back to `/logout` after they
|
|
5022
|
+
* signed in defeats the sign-in, and back to `/login` loops. The exclusion used to be a literal
|
|
5023
|
+
* `!== "/login"`, which let through `/logout`, `/login?mode=changepassword` — same screen, one
|
|
5024
|
+
* query param — and any application whose `loginPath` is not spelled `/login`.
|
|
5025
|
+
*/
|
|
5026
|
+
get urlAfterNavigation() {
|
|
5027
|
+
const url = this.router.url;
|
|
5028
|
+
return isAuthRoute(url, globalConfig.loginPath) ? null : url;
|
|
5029
|
+
}
|
|
5030
|
+
/**
|
|
5031
|
+
* Observable that emits events of type `NavigationEnd` from the Angular Router.
|
|
5032
|
+
*
|
|
5033
|
+
* This observable performs the following operations:
|
|
5034
|
+
* - Maps all router events to `RouterEvent`.
|
|
5035
|
+
* - Filters the events to only include instances of `NavigationEnd`.
|
|
5036
|
+
* - Taps into the event stream to notify the audit service when the route itself changes — a new
|
|
5037
|
+
* query, a filter or another page of results stays on the same route and is not one.
|
|
5038
|
+
* - Shares the replayed value with a buffer size of 1 to ensure subscribers receive the latest emitted value.
|
|
5039
|
+
*
|
|
5040
|
+
* @type Observable<RouterEvent>
|
|
5041
|
+
*/
|
|
5042
|
+
navigationEnd$ = this.router.events.pipe(map(event => event), filter$1((event) => event instanceof NavigationEnd), tap(event => {
|
|
5043
|
+
const routeName = routeNameOf(event.url);
|
|
5044
|
+
// The empty name is the application root, which has never been audited.
|
|
5045
|
+
if (routeName && routeName !== this.previousRouteName) {
|
|
5046
|
+
this.auditService.notifyRouteChange(routeName);
|
|
5047
|
+
}
|
|
5048
|
+
// Every route is remembered, the authentication screens included. Skipping them would swallow
|
|
5049
|
+
// the return: leaving `/search` for `/login` and coming back is two route changes, and the
|
|
5050
|
+
// second one would compare against a `search` the user has meanwhile left. (The exclusion that
|
|
5051
|
+
// used to sit here never ran — the dedupe above it could not fire.)
|
|
5052
|
+
this.previousRouteName = routeName;
|
|
5053
|
+
}), shareReplay(1));
|
|
5054
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NavigationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
5055
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NavigationService, providedIn: "root" });
|
|
5056
|
+
}
|
|
5057
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: NavigationService, decorators: [{
|
|
5058
|
+
type: Injectable,
|
|
5059
|
+
args: [{
|
|
5060
|
+
providedIn: "root"
|
|
5061
|
+
}]
|
|
5062
|
+
}] });
|
|
5063
|
+
|
|
4976
5064
|
/**
|
|
4977
5065
|
* @deprecated This service is deprecated and should not be used directly. Please use the PrincipalStore instead.
|
|
4978
5066
|
*/
|
|
@@ -10281,12 +10369,13 @@ class ArticleEntities {
|
|
|
10281
10369
|
cn = cn;
|
|
10282
10370
|
appStore = inject(AppStore);
|
|
10283
10371
|
previewService = inject(PreviewService);
|
|
10372
|
+
entityNavigation = inject(EntityNavigationService);
|
|
10284
10373
|
article = input.required(...(ngDevMode ? [{ debugName: "article" }] : []));
|
|
10285
10374
|
previewHighlights = computed(() => {
|
|
10286
10375
|
const highlights = this.appStore.getWebServiceByType("preview")?.highlights
|
|
10287
10376
|
?.split(",")
|
|
10288
|
-
.filter(
|
|
10289
|
-
return highlights?.map(
|
|
10377
|
+
.filter(h => h !== "extractslocations" && h !== "matchlocations" && h !== "matchingpassages");
|
|
10378
|
+
return highlights?.map(highlight => ({
|
|
10290
10379
|
name: highlight,
|
|
10291
10380
|
entity: highlight,
|
|
10292
10381
|
metadata: this.getMetadata(this.article(), highlight)
|
|
@@ -10296,54 +10385,24 @@ class ArticleEntities {
|
|
|
10296
10385
|
const m = article[entity];
|
|
10297
10386
|
return m ?? [];
|
|
10298
10387
|
}
|
|
10299
|
-
navigation =
|
|
10300
|
-
hovering =
|
|
10301
|
-
hoverIndex =
|
|
10388
|
+
navigation = this.entityNavigation.navigation;
|
|
10389
|
+
hovering = this.entityNavigation.hovering;
|
|
10390
|
+
hoverIndex = this.entityNavigation.hoverIndex;
|
|
10391
|
+
/** Number of occurrences of the entity value highlighted in the preview (not the indexed `count`, see ES-32245) */
|
|
10392
|
+
total(entity, data) {
|
|
10393
|
+
return this.entityNavigation.total(entity, data.value, data.count);
|
|
10394
|
+
}
|
|
10302
10395
|
scrollTo(type, index, usePassageHighlighter = false) {
|
|
10303
10396
|
// if previewData contains "matchingpassages" then we use the passage highlighter otherwise we use "extractslocations"
|
|
10304
10397
|
const category = type || this.previewService.highlightCategory;
|
|
10305
10398
|
this.previewService.events.set("scrollTo");
|
|
10306
10399
|
this.previewService.sendMessage({ action: "select", id: `${category}_${index}`, usePassageHighlighter });
|
|
10307
10400
|
}
|
|
10308
|
-
navigate(entity, data, direction) {
|
|
10309
|
-
if (!data.count || data.count <= 0) {
|
|
10310
|
-
warn(`Invalid count for entity ${entity}:`, data.count);
|
|
10311
|
-
return;
|
|
10312
|
-
}
|
|
10313
|
-
const currentNavigation = this.navigation();
|
|
10314
|
-
const isCurrentValue = currentNavigation?.value === data.value;
|
|
10315
|
-
let newIndex;
|
|
10316
|
-
if (direction === "next") {
|
|
10317
|
-
newIndex = isCurrentValue && currentNavigation.index < data.count ? currentNavigation.index + 1 : 1;
|
|
10318
|
-
}
|
|
10319
|
-
else {
|
|
10320
|
-
newIndex = isCurrentValue && currentNavigation.index > 1 ? currentNavigation.index - 1 : data.count;
|
|
10321
|
-
}
|
|
10322
|
-
this.navigation.set({
|
|
10323
|
-
value: data.value,
|
|
10324
|
-
index: newIndex
|
|
10325
|
-
});
|
|
10326
|
-
const id = this.previewService.getEntityId(entity, data.value, newIndex - 1);
|
|
10327
|
-
if (id !== undefined) {
|
|
10328
|
-
this.scrollTo(entity, id);
|
|
10329
|
-
debug(`${direction} navigation:`, {
|
|
10330
|
-
id,
|
|
10331
|
-
entity,
|
|
10332
|
-
value: data.value,
|
|
10333
|
-
index: newIndex,
|
|
10334
|
-
totalCount: data.count
|
|
10335
|
-
});
|
|
10336
|
-
}
|
|
10337
|
-
else {
|
|
10338
|
-
warn(`Unable to get ID for ${direction} navigation:`, { entity, value: data.value, index: newIndex });
|
|
10339
|
-
notify.warning(`Unable to navigate to the ${direction} occurrence of "${data.value}".`);
|
|
10340
|
-
}
|
|
10341
|
-
}
|
|
10342
10401
|
navigateNext(entity, data) {
|
|
10343
|
-
this.navigate(entity, data, "next");
|
|
10402
|
+
this.entityNavigation.navigate(entity, data.value, "next");
|
|
10344
10403
|
}
|
|
10345
10404
|
navigatePrev(entity, data) {
|
|
10346
|
-
this.navigate(entity, data, "prev");
|
|
10405
|
+
this.entityNavigation.navigate(entity, data.value, "prev");
|
|
10347
10406
|
}
|
|
10348
10407
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: ArticleEntities, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10349
10408
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: ArticleEntities, isStandalone: true, selector: "article-entities,ArticleEntities,articleentities", inputs: { article: { classPropertyName: "article", publicName: "article", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
@@ -10387,13 +10446,13 @@ class ArticleEntities {
|
|
|
10387
10446
|
<chevronleft />
|
|
10388
10447
|
</button>
|
|
10389
10448
|
|
|
10390
|
-
<span>{{ hoverIndex() }} / {{ data
|
|
10449
|
+
<span>{{ hoverIndex() }} / {{ total(entity, data) }}</span>
|
|
10391
10450
|
|
|
10392
10451
|
<button class="hover:scale-125" aria-label="next entity" role="button" (click)="navigateNext(entity, data)">
|
|
10393
10452
|
<chevronright />
|
|
10394
10453
|
</button>
|
|
10395
10454
|
} @else {
|
|
10396
|
-
{{ data
|
|
10455
|
+
{{ total(entity, data) }}
|
|
10397
10456
|
}
|
|
10398
10457
|
</p>
|
|
10399
10458
|
</li>
|
|
@@ -10449,13 +10508,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
10449
10508
|
<chevronleft />
|
|
10450
10509
|
</button>
|
|
10451
10510
|
|
|
10452
|
-
<span>{{ hoverIndex() }} / {{ data
|
|
10511
|
+
<span>{{ hoverIndex() }} / {{ total(entity, data) }}</span>
|
|
10453
10512
|
|
|
10454
10513
|
<button class="hover:scale-125" aria-label="next entity" role="button" (click)="navigateNext(entity, data)">
|
|
10455
10514
|
<chevronright />
|
|
10456
10515
|
</button>
|
|
10457
10516
|
} @else {
|
|
10458
|
-
{{ data
|
|
10517
|
+
{{ total(entity, data) }}
|
|
10459
10518
|
}
|
|
10460
10519
|
</p>
|
|
10461
10520
|
</li>
|
|
@@ -13654,12 +13713,13 @@ class EntitiesPanel {
|
|
|
13654
13713
|
cn = cn;
|
|
13655
13714
|
appStore = inject(AppStore);
|
|
13656
13715
|
previewService = inject(PreviewService);
|
|
13716
|
+
entityNavigation = inject(EntityNavigationService);
|
|
13657
13717
|
article = input.required(...(ngDevMode ? [{ debugName: "article" }] : []));
|
|
13658
13718
|
previewHighlights = computed(() => {
|
|
13659
13719
|
const highlights = this.appStore.getWebServiceByType("preview")?.highlights
|
|
13660
13720
|
?.split(",")
|
|
13661
|
-
.filter(
|
|
13662
|
-
return highlights?.map(
|
|
13721
|
+
.filter(h => h !== "extractslocations" && h !== "matchlocations" && h !== "matchingpassages");
|
|
13722
|
+
return highlights?.map(highlight => ({
|
|
13663
13723
|
name: highlight,
|
|
13664
13724
|
entity: highlight,
|
|
13665
13725
|
metadata: this.getMetadata(this.article(), highlight)
|
|
@@ -13669,54 +13729,24 @@ class EntitiesPanel {
|
|
|
13669
13729
|
const m = article[entity];
|
|
13670
13730
|
return m ?? [];
|
|
13671
13731
|
}
|
|
13672
|
-
navigation =
|
|
13673
|
-
hovering =
|
|
13674
|
-
hoverIndex =
|
|
13732
|
+
navigation = this.entityNavigation.navigation;
|
|
13733
|
+
hovering = this.entityNavigation.hovering;
|
|
13734
|
+
hoverIndex = this.entityNavigation.hoverIndex;
|
|
13735
|
+
/** Number of occurrences of the entity value highlighted in the preview (not the indexed `count`, see ES-32245) */
|
|
13736
|
+
total(entity, data) {
|
|
13737
|
+
return this.entityNavigation.total(entity, data.value, data.count);
|
|
13738
|
+
}
|
|
13675
13739
|
scrollTo(type, index, usePassageHighlighter = false) {
|
|
13676
13740
|
// if previewData contains "matchingpassages" then we use the passage highlighter otherwise we use "extractslocations"
|
|
13677
13741
|
const category = type || this.previewService.highlightCategory;
|
|
13678
13742
|
this.previewService.events.set("scrollTo");
|
|
13679
13743
|
this.previewService.sendMessage({ action: "select", id: `${category}_${index}`, usePassageHighlighter });
|
|
13680
13744
|
}
|
|
13681
|
-
navigate(entity, data, direction) {
|
|
13682
|
-
if (!data.count || data.count <= 0) {
|
|
13683
|
-
warn(`Invalid count for entity ${entity}:`, data.count);
|
|
13684
|
-
return;
|
|
13685
|
-
}
|
|
13686
|
-
const currentNavigation = this.navigation();
|
|
13687
|
-
const isCurrentValue = currentNavigation?.value === data.value;
|
|
13688
|
-
let newIndex;
|
|
13689
|
-
if (direction === "next") {
|
|
13690
|
-
newIndex = isCurrentValue && currentNavigation.index < data.count ? currentNavigation.index + 1 : 1;
|
|
13691
|
-
}
|
|
13692
|
-
else {
|
|
13693
|
-
newIndex = isCurrentValue && currentNavigation.index > 1 ? currentNavigation.index - 1 : data.count;
|
|
13694
|
-
}
|
|
13695
|
-
this.navigation.set({
|
|
13696
|
-
value: data.value,
|
|
13697
|
-
index: newIndex
|
|
13698
|
-
});
|
|
13699
|
-
const id = this.previewService.getEntityId(entity, data.value, newIndex - 1);
|
|
13700
|
-
if (id !== undefined) {
|
|
13701
|
-
this.scrollTo(entity, id);
|
|
13702
|
-
debug(`${direction} navigation:`, {
|
|
13703
|
-
id,
|
|
13704
|
-
entity,
|
|
13705
|
-
value: data.value,
|
|
13706
|
-
index: newIndex,
|
|
13707
|
-
totalCount: data.count
|
|
13708
|
-
});
|
|
13709
|
-
}
|
|
13710
|
-
else {
|
|
13711
|
-
warn(`Unable to get ID for ${direction} navigation:`, { entity, value: data.value, index: newIndex });
|
|
13712
|
-
notify.warning(`Unable to navigate to the ${direction} occurrence of "${data.value}".`);
|
|
13713
|
-
}
|
|
13714
|
-
}
|
|
13715
13745
|
navigateNext(entity, data) {
|
|
13716
|
-
this.navigate(entity, data, "next");
|
|
13746
|
+
this.entityNavigation.navigate(entity, data.value, "next");
|
|
13717
13747
|
}
|
|
13718
13748
|
navigatePrev(entity, data) {
|
|
13719
|
-
this.navigate(entity, data, "prev");
|
|
13749
|
+
this.entityNavigation.navigate(entity, data.value, "prev");
|
|
13720
13750
|
}
|
|
13721
13751
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: EntitiesPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13722
13752
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: EntitiesPanel, isStandalone: true, selector: "entities-panel,EntitiesPanel,entitiesPanel", inputs: { article: { classPropertyName: "article", publicName: "article", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
@@ -13760,13 +13790,13 @@ class EntitiesPanel {
|
|
|
13760
13790
|
<chevronleft />
|
|
13761
13791
|
</button>
|
|
13762
13792
|
|
|
13763
|
-
<span>{{ hoverIndex() }} / {{ data
|
|
13793
|
+
<span>{{ hoverIndex() }} / {{ total(entity, data) }}</span>
|
|
13764
13794
|
|
|
13765
13795
|
<button class="hover:scale-125" aria-label="next entity" role="button" (click)="navigateNext(entity, data)">
|
|
13766
13796
|
<chevronright />
|
|
13767
13797
|
</button>
|
|
13768
13798
|
} @else {
|
|
13769
|
-
{{ data
|
|
13799
|
+
{{ total(entity, data) }}
|
|
13770
13800
|
}
|
|
13771
13801
|
</p>
|
|
13772
13802
|
</li>
|
|
@@ -13822,13 +13852,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
13822
13852
|
<chevronleft />
|
|
13823
13853
|
</button>
|
|
13824
13854
|
|
|
13825
|
-
<span>{{ hoverIndex() }} / {{ data
|
|
13855
|
+
<span>{{ hoverIndex() }} / {{ total(entity, data) }}</span>
|
|
13826
13856
|
|
|
13827
13857
|
<button class="hover:scale-125" aria-label="next entity" role="button" (click)="navigateNext(entity, data)">
|
|
13828
13858
|
<chevronright />
|
|
13829
13859
|
</button>
|
|
13830
13860
|
} @else {
|
|
13831
|
-
{{ data
|
|
13861
|
+
{{ total(entity, data) }}
|
|
13832
13862
|
}
|
|
13833
13863
|
</p>
|
|
13834
13864
|
</li>
|
|
@@ -18383,6 +18413,18 @@ class UserProfileFormComponent {
|
|
|
18383
18413
|
this.feedback.set(undefined);
|
|
18384
18414
|
this.propertyToEdit.set(undefined);
|
|
18385
18415
|
}
|
|
18416
|
+
/**
|
|
18417
|
+
* Whether the profile holds a value for a property. Only then is there something to delete: a property
|
|
18418
|
+
* that shows its configured default, or that is simply not set, is absent from the profile and the
|
|
18419
|
+
* server answers 404 to a delete on it (ES-34462).
|
|
18420
|
+
*
|
|
18421
|
+
* @param category data or customData
|
|
18422
|
+
* @param propertyName property name
|
|
18423
|
+
*/
|
|
18424
|
+
hasStoredValue(category, propertyName) {
|
|
18425
|
+
const stored = this.userProfile()?.[category]?.[propertyName];
|
|
18426
|
+
return stored !== undefined && stored !== null;
|
|
18427
|
+
}
|
|
18386
18428
|
/**
|
|
18387
18429
|
* On data property deletion, for now we make the property an empty string
|
|
18388
18430
|
*
|
|
@@ -18392,6 +18434,11 @@ class UserProfileFormComponent {
|
|
|
18392
18434
|
async onDeleteData(category, propertyName) {
|
|
18393
18435
|
if (this.pending())
|
|
18394
18436
|
return;
|
|
18437
|
+
// Nothing stored, nothing to delete: not a failure, and no request to send.
|
|
18438
|
+
if (!this.hasStoredValue(category, propertyName)) {
|
|
18439
|
+
info("User profile property not stored, nothing to delete", { category, propertyName });
|
|
18440
|
+
return;
|
|
18441
|
+
}
|
|
18395
18442
|
const key = `${category}.${propertyName}`;
|
|
18396
18443
|
this.feedback.set(undefined);
|
|
18397
18444
|
this.pending.set(key);
|
|
@@ -18399,9 +18446,7 @@ class UserProfileFormComponent {
|
|
|
18399
18446
|
const response = await deleteUserProfileProperty(this.userProfile(), category, propertyName);
|
|
18400
18447
|
if (response) {
|
|
18401
18448
|
info("Deleted user profile property", response);
|
|
18402
|
-
|
|
18403
|
-
userProfile[category][propertyName] = undefined;
|
|
18404
|
-
this.userProfileResource.set(userProfile);
|
|
18449
|
+
this.forgetProperty(category, propertyName);
|
|
18405
18450
|
this.feedback.set({ key, type: "success", message: this.message("deleted", category, propertyName) });
|
|
18406
18451
|
}
|
|
18407
18452
|
else {
|
|
@@ -18409,13 +18454,30 @@ class UserProfileFormComponent {
|
|
|
18409
18454
|
this.feedback.set({ key, type: "error", message: this.message("deleteFailed", category, propertyName) });
|
|
18410
18455
|
}
|
|
18411
18456
|
}
|
|
18412
|
-
catch {
|
|
18413
|
-
|
|
18457
|
+
catch (err) {
|
|
18458
|
+
if (err?.status === 404) {
|
|
18459
|
+
// The property is not in the profile (anymore), e.g. removed from another tab: the state asked for is
|
|
18460
|
+
// already reached, so it is not an error to show.
|
|
18461
|
+
info("User profile property already absent", { category, propertyName });
|
|
18462
|
+
this.forgetProperty(category, propertyName);
|
|
18463
|
+
this.feedback.set({ key, type: "success", message: this.message("deleted", category, propertyName) });
|
|
18464
|
+
}
|
|
18465
|
+
else {
|
|
18466
|
+
this.feedback.set({ key, type: "error", message: this.message("deleteFailed", category, propertyName) });
|
|
18467
|
+
}
|
|
18414
18468
|
}
|
|
18415
18469
|
finally {
|
|
18416
18470
|
this.pending.set(undefined);
|
|
18417
18471
|
}
|
|
18418
18472
|
}
|
|
18473
|
+
/** Takes a deleted property out of the stored profile; a custom property with a default shows that default again. */
|
|
18474
|
+
forgetProperty(category, propertyName) {
|
|
18475
|
+
const userProfile = JSON.parse(JSON.stringify(this.userProfile()));
|
|
18476
|
+
if (userProfile[category]) {
|
|
18477
|
+
userProfile[category][propertyName] = undefined;
|
|
18478
|
+
}
|
|
18479
|
+
this.userProfileResource.set(userProfile);
|
|
18480
|
+
}
|
|
18419
18481
|
/**
|
|
18420
18482
|
* Saving a data property
|
|
18421
18483
|
*
|
|
@@ -18432,18 +18494,11 @@ class UserProfileFormComponent {
|
|
|
18432
18494
|
const userProfile = JSON.parse(JSON.stringify(this.userProfile()));
|
|
18433
18495
|
if (!userProfile[category])
|
|
18434
18496
|
userProfile[category] = {};
|
|
18435
|
-
|
|
18497
|
+
const written = this.value()?.trim() || "";
|
|
18498
|
+
userProfile[category][propertyName] = written;
|
|
18436
18499
|
try {
|
|
18437
18500
|
const response = await patchUserProfile(userProfile, category, propertyName);
|
|
18438
|
-
|
|
18439
|
-
const newUserProfile = this.userProfile();
|
|
18440
|
-
this.userProfileResource.set({
|
|
18441
|
-
...newUserProfile,
|
|
18442
|
-
...response,
|
|
18443
|
-
data: { ...(newUserProfile?.data || {}), ...(response.data || {}) },
|
|
18444
|
-
customData: { ...(newUserProfile?.customData || {}), ...(response.customData || {}) }
|
|
18445
|
-
});
|
|
18446
|
-
}
|
|
18501
|
+
this.foldSavedValue(category, propertyName, response, written);
|
|
18447
18502
|
this.value.set(undefined);
|
|
18448
18503
|
this.propertyToEdit.set(undefined);
|
|
18449
18504
|
this.feedback.set({ key, type: "success", message: this.message("saved", category, propertyName) });
|
|
@@ -18455,6 +18510,32 @@ class UserProfileFormComponent {
|
|
|
18455
18510
|
this.pending.set(undefined);
|
|
18456
18511
|
}
|
|
18457
18512
|
}
|
|
18513
|
+
/**
|
|
18514
|
+
* Folds the answer of a PATCH back into the stored profile, so the form shows what was just saved.
|
|
18515
|
+
*
|
|
18516
|
+
* The answer depends on the server: before ES-34373 it is the whole updated profile (`{ data, customData, … }`),
|
|
18517
|
+
* after it is the written value alone (`"fav-1AAA"`). Reading the latter as a profile used to leave the stored
|
|
18518
|
+
* profile unchanged, and the form kept showing the old value (or the default) until the page was reloaded.
|
|
18519
|
+
*/
|
|
18520
|
+
foldSavedValue(category, propertyName, response, written) {
|
|
18521
|
+
const current = this.userProfile();
|
|
18522
|
+
if (isPlainObject(response) && ("data" in response || "customData" in response)) {
|
|
18523
|
+
const profile = response;
|
|
18524
|
+
this.userProfileResource.set({
|
|
18525
|
+
...current,
|
|
18526
|
+
...profile,
|
|
18527
|
+
data: { ...(current?.data || {}), ...(profile.data || {}) },
|
|
18528
|
+
customData: { ...(current?.customData || {}), ...(profile.customData || {}) }
|
|
18529
|
+
});
|
|
18530
|
+
return;
|
|
18531
|
+
}
|
|
18532
|
+
// The value alone: what the server read back, or what was sent when it answers with nothing usable.
|
|
18533
|
+
const value = response === undefined || response === null ? written : response;
|
|
18534
|
+
this.userProfileResource.set({
|
|
18535
|
+
...current,
|
|
18536
|
+
[category]: { ...(current?.[category] || {}), [propertyName]: value }
|
|
18537
|
+
});
|
|
18538
|
+
}
|
|
18458
18539
|
/**
|
|
18459
18540
|
* Get the value of a data key
|
|
18460
18541
|
*
|
|
@@ -18501,7 +18582,7 @@ class UserProfileFormComponent {
|
|
|
18501
18582
|
}
|
|
18502
18583
|
}
|
|
18503
18584
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: UserProfileFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
18504
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: UserProfileFormComponent, isStandalone: true, selector: "user-profile-form, UserProfileForm, userprofileform", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, currentLanguage: { classPropertyName: "currentLanguage", publicName: "currentLanguage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", currentLanguage: "currentLanguageChange" }, host: { classAttribute: "flex flex-col h-full overflow-hidden" }, providers: [provideTranslocoScope("user-profile", "login")], viewQueries: [{ propertyName: "createInputElement", first: true, predicate: ["avatarInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n <!-- AVATAR -->\n <Avatar\n class=\"size-14 cursor-pointer\"\n [class.ring-2]=\"!!fieldState('data.profilePhoto')\"\n [class.ring-destructive]=\"fieldState('data.profilePhoto') === 'error'\"\n [class.ring-green-600]=\"fieldState('data.profilePhoto') === 'success'\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage\n [src]=\"profile?.data?.profilePhoto!\"\n width=\"44\"\n height=\"44\"\n alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n @if (profile?.data?.profilePhoto) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\"\n (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n <!-- always rendered, so the room is reserved and the layout doesn't shift when a message appears -->\n <p\n class=\"min-h-5 text-sm\"\n role=\"status\"\n [attr.aria-live]=\"feedback()?.type === 'error' ? 'assertive' : 'polite'\"\n [class.text-destructive]=\"feedback()?.type === 'error'\"\n [class.text-green-600]=\"feedback()?.type === 'success'\">\n {{ feedback()?.message }}\n </p>\n @if (changingPassword()) {\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- -m-1 p-1: room for the outline of a field, which the scroll container would otherwise clip -->\n <div class=\"-m-1 min-h-0 flex-1 overflow-auto p-1\">\n <div>\n @for (key of formKeys(); track key) {\n <div>\n <p class=\"px-2\">{{ `userProfile.data.${key}` | transloco }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('data.' + key)\"\n [class.ring-destructive]=\"fieldState('data.' + key) === 'error'\"\n [class.ring-green-600]=\"\n fieldState('data.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === \"data.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"data\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"data.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('data', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p class=\"px-2\">{{ key }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('customData.' + key)\"\n [class.ring-destructive]=\"\n fieldState('customData.' + key) === 'error'\n \"\n [class.ring-green-600]=\"\n fieldState('customData.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"\n !getDataValue('customData', key)\n \">\n @if (propertyToEdit() === \"customData.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"customData\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"customData.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('customData', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n} @else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: TrashIcon, selector: "trash-icon, TrashIcon", inputs: ["class"] }, { kind: "component", type: EditIcon, selector: "edit-icon, EditIcon, editicon", inputs: ["class"] }, { kind: "component", type: UndoIcon, selector: "UndoIcon, undoicon, undo-icon", inputs: ["class"] }, { kind: "component", type: SaveIcon, selector: "save-icon, SaveIcon", inputs: ["class"] }, { kind: "component", type: AvatarComponent, selector: "avatar, Avatar", inputs: ["class", "size"] }, { kind: "component", type: AvatarFallbackComponent, selector: "avatar-fallback, avatarfallback, AvatarFallback", inputs: ["class", "size"] }, { kind: "component", type: UserIcon, selector: "user-icon, UserIcon", inputs: ["class"] }, { kind: "component", type: AvatarImageComponent, selector: "avatar-image, AvatarImage, avatarimage", inputs: ["class", "src", "alt", "width", "height", "referrerPolicy", "crossOrigin"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: ChangePasswordComponent, selector: "change-password, ChangePassword, changepassword", inputs: ["username", "alert", "redirectAfterSuccess", "redirectAfterCancel", "currentPassword", "newPassword", "confirmPassword"], outputs: ["success", "cancel", "currentPasswordChange", "newPasswordChange", "confirmPasswordChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
18585
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.29", type: UserProfileFormComponent, isStandalone: true, selector: "user-profile-form, UserProfileForm, userprofileform", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, currentLanguage: { classPropertyName: "currentLanguage", publicName: "currentLanguage", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", currentLanguage: "currentLanguageChange" }, host: { classAttribute: "flex flex-col h-full overflow-hidden" }, providers: [provideTranslocoScope("user-profile", "login")], viewQueries: [{ propertyName: "createInputElement", first: true, predicate: ["avatarInput"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n <!-- AVATAR -->\n <Avatar\n class=\"size-14 cursor-pointer\"\n [class.ring-2]=\"!!fieldState('data.profilePhoto')\"\n [class.ring-destructive]=\"fieldState('data.profilePhoto') === 'error'\"\n [class.ring-green-600]=\"fieldState('data.profilePhoto') === 'success'\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage\n [src]=\"profile?.data?.profilePhoto!\"\n width=\"44\"\n height=\"44\"\n alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n @if (profile?.data?.profilePhoto) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\"\n (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n <!-- always rendered, so the room is reserved and the layout doesn't shift when a message appears -->\n <p\n class=\"min-h-5 text-sm\"\n role=\"status\"\n [attr.aria-live]=\"feedback()?.type === 'error' ? 'assertive' : 'polite'\"\n [class.text-destructive]=\"feedback()?.type === 'error'\"\n [class.text-green-600]=\"feedback()?.type === 'success'\">\n {{ feedback()?.message }}\n </p>\n @if (changingPassword()) {\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- -m-1 p-1: room for the outline of a field, which the scroll container would otherwise clip -->\n <div class=\"-m-1 min-h-0 flex-1 overflow-auto p-1\">\n <div>\n @for (key of formKeys(); track key) {\n <div>\n <p class=\"px-2\">{{ `userProfile.data.${key}` | transloco }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('data.' + key)\"\n [class.ring-destructive]=\"fieldState('data.' + key) === 'error'\"\n [class.ring-green-600]=\"\n fieldState('data.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === \"data.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"data\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"data.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('data', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending() || !hasStoredValue('data', key)\"\n (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p class=\"px-2\">{{ key }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('customData.' + key)\"\n [class.ring-destructive]=\"\n fieldState('customData.' + key) === 'error'\n \"\n [class.ring-green-600]=\"\n fieldState('customData.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"\n !getDataValue('customData', key)\n \">\n @if (propertyToEdit() === \"customData.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"customData\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"customData.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('customData', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"\n !!pending() || !hasStoredValue('customData', key)\n \"\n (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n} @else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: TrashIcon, selector: "trash-icon, TrashIcon", inputs: ["class"] }, { kind: "component", type: EditIcon, selector: "edit-icon, EditIcon, editicon", inputs: ["class"] }, { kind: "component", type: UndoIcon, selector: "UndoIcon, undoicon, undo-icon", inputs: ["class"] }, { kind: "component", type: SaveIcon, selector: "save-icon, SaveIcon", inputs: ["class"] }, { kind: "component", type: AvatarComponent, selector: "avatar, Avatar", inputs: ["class", "size"] }, { kind: "component", type: AvatarFallbackComponent, selector: "avatar-fallback, avatarfallback, AvatarFallback", inputs: ["class", "size"] }, { kind: "component", type: UserIcon, selector: "user-icon, UserIcon", inputs: ["class"] }, { kind: "component", type: AvatarImageComponent, selector: "avatar-image, AvatarImage, avatarimage", inputs: ["class", "src", "alt", "width", "height", "referrerPolicy", "crossOrigin"] }, { kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "component", type: ChangePasswordComponent, selector: "change-password, ChangePassword, changepassword", inputs: ["username", "alert", "redirectAfterSuccess", "redirectAfterCancel", "currentPassword", "newPassword", "confirmPassword"], outputs: ["success", "cancel", "currentPasswordChange", "newPasswordChange", "confirmPasswordChange"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }] });
|
|
18505
18586
|
}
|
|
18506
18587
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImport: i0, type: UserProfileFormComponent, decorators: [{
|
|
18507
18588
|
type: Component,
|
|
@@ -18519,7 +18600,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.29", ngImpo
|
|
|
18519
18600
|
AvatarImageComponent,
|
|
18520
18601
|
ButtonComponent,
|
|
18521
18602
|
ChangePasswordComponent
|
|
18522
|
-
], providers: [provideTranslocoScope("user-profile", "login")], template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n <!-- AVATAR -->\n <Avatar\n class=\"size-14 cursor-pointer\"\n [class.ring-2]=\"!!fieldState('data.profilePhoto')\"\n [class.ring-destructive]=\"fieldState('data.profilePhoto') === 'error'\"\n [class.ring-green-600]=\"fieldState('data.profilePhoto') === 'success'\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage\n [src]=\"profile?.data?.profilePhoto!\"\n width=\"44\"\n height=\"44\"\n alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n @if (profile?.data?.profilePhoto) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\"\n (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n <!-- always rendered, so the room is reserved and the layout doesn't shift when a message appears -->\n <p\n class=\"min-h-5 text-sm\"\n role=\"status\"\n [attr.aria-live]=\"feedback()?.type === 'error' ? 'assertive' : 'polite'\"\n [class.text-destructive]=\"feedback()?.type === 'error'\"\n [class.text-green-600]=\"feedback()?.type === 'success'\">\n {{ feedback()?.message }}\n </p>\n @if (changingPassword()) {\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- -m-1 p-1: room for the outline of a field, which the scroll container would otherwise clip -->\n <div class=\"-m-1 min-h-0 flex-1 overflow-auto p-1\">\n <div>\n @for (key of formKeys(); track key) {\n <div>\n <p class=\"px-2\">{{ `userProfile.data.${key}` | transloco }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('data.' + key)\"\n [class.ring-destructive]=\"fieldState('data.' + key) === 'error'\"\n [class.ring-green-600]=\"\n fieldState('data.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === \"data.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"data\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"data.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('data', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p class=\"px-2\">{{ key }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('customData.' + key)\"\n [class.ring-destructive]=\"\n fieldState('customData.' + key) === 'error'\n \"\n [class.ring-green-600]=\"\n fieldState('customData.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"\n !getDataValue('customData', key)\n \">\n @if (propertyToEdit() === \"customData.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"customData\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"customData.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('customData', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n} @else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n" }]
|
|
18603
|
+
], providers: [provideTranslocoScope("user-profile", "login")], template: "@if (userProfileResource.hasValue()) {\n @let profile = userProfile();\n <div class=\"flex min-h-0 flex-1 flex-col gap-2 p-4\">\n <div class=\"mb-4 flex\">\n @if (allowProfilePhoto()) {\n <!-- AVATAR -->\n <Avatar\n class=\"size-14 cursor-pointer\"\n [class.ring-2]=\"!!fieldState('data.profilePhoto')\"\n [class.ring-destructive]=\"fieldState('data.profilePhoto') === 'error'\"\n [class.ring-green-600]=\"fieldState('data.profilePhoto') === 'success'\"\n (click)=\"avatarInput.click()\">\n @if (profile?.data?.profilePhoto) {\n <AvatarImage\n [src]=\"profile?.data?.profilePhoto!\"\n width=\"44\"\n height=\"44\"\n alt=\"avatar\" />\n }\n <AvatarFallback class=\"text-lg\">\n @if (initials()) {\n <span>{{ initials() }}</span>\n } @else {\n <UserIcon class=\"size-7\" />\n }\n </AvatarFallback>\n </Avatar>\n @if (profile?.data?.profilePhoto) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive cursor-pointer\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onDeleteData('data', 'profilePhoto')\">\n <TrashIcon />\n </button>\n }\n }\n <div class=\"grow\"></div>\n <div class=\"flex flex-col gap-4\">\n <!-- LANGUAGE -->\n <select\n class=\"hover:outline-primary focus:outline-primary border-foreground/10 bg-background hover:bg-muted focus:bg-muted h-8 rounded-md border px-2 hover:outline focus:outline\"\n [(ngModel)]=\"currentLanguage\"\n (change)=\"changeLanguage()\">\n @for (lang of AllLanguages; track lang.code) {\n <option [value]=\"lang.code\">\n {{ lang.label }}\n </option>\n }\n </select>\n <!-- CHANGE PASSWORD -->\n @if (allowChangePassword()) {\n <button\n [disabled]=\"changingPassword()\"\n (click)=\"changingPassword.set(true)\">\n {{ \"login.changePassword\" | transloco }}\n </button>\n }\n </div>\n </div>\n <!-- always rendered, so the room is reserved and the layout doesn't shift when a message appears -->\n <p\n class=\"min-h-5 text-sm\"\n role=\"status\"\n [attr.aria-live]=\"feedback()?.type === 'error' ? 'assertive' : 'polite'\"\n [class.text-destructive]=\"feedback()?.type === 'error'\"\n [class.text-green-600]=\"feedback()?.type === 'success'\">\n {{ feedback()?.message }}\n </p>\n @if (changingPassword()) {\n <ChangePassword\n [username]=\"username()\"\n [redirectAfterSuccess]=\"false\"\n [redirectAfterCancel]=\"false\"\n (cancel)=\"changingPassword.set(false)\"\n (success)=\"changingPassword.set(false)\" />\n } @else {\n <!-- -m-1 p-1: room for the outline of a field, which the scroll container would otherwise clip -->\n <div class=\"-m-1 min-h-0 flex-1 overflow-auto p-1\">\n <div>\n @for (key of formKeys(); track key) {\n <div>\n <p class=\"px-2\">{{ `userProfile.data.${key}` | transloco }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('data.' + key)\"\n [class.ring-destructive]=\"fieldState('data.' + key) === 'error'\"\n [class.ring-green-600]=\"\n fieldState('data.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"!getDataValue('data', key)\">\n @if (propertyToEdit() === \"data.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"data\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"data.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('data', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('data', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"!!pending() || !hasStoredValue('data', key)\"\n (click)=\"onDeleteData('data', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n @for (key of customData(); track key) {\n <div>\n <p class=\"px-2\">{{ key }}</p>\n <div\n class=\"group flex flex-row rounded-md px-2 py-0.5\"\n [class.ring-1]=\"!!fieldState('customData.' + key)\"\n [class.ring-destructive]=\"\n fieldState('customData.' + key) === 'error'\n \"\n [class.ring-green-600]=\"\n fieldState('customData.' + key) === 'success'\n \">\n <div\n class=\"min-w-0 grow break-words whitespace-pre-line\"\n [class.text-muted-foreground]=\"\n !getDataValue('customData', key)\n \">\n @if (propertyToEdit() === \"customData.\" + key) {\n <textarea\n class=\"hover:outline-primary focus:outline-primary border-foreground/20 hover:bg-muted focus:bg-muted mt-2 w-full rounded-md border px-2 pt-1 hover:outline focus:outline\"\n id=\"user-profile-{{ key }}\"\n [(ngModel)]=\"value\"></textarea>\n } @else {\n {{\n getDataValue(\"customData\", key) ||\n (\"userProfile.notDefined\" | transloco)\n }}\n }\n </div>\n @if (propertyToEdit() === \"customData.\" + key) {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n [disabled]=\"!!pending()\"\n (click)=\"onSaveData('customData', key)\">\n <save-icon class=\"ml-2\" />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onCancelEdit()\">\n <UndoIcon />\n </button>\n } @else {\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"visible mx-2 cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.editProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.editProperty' | transloco\"\n (click)=\"onEdit('customData', key)\">\n <EditIcon />\n </button>\n <button\n variant=\"ghost\"\n [iconOnly]=\"true\"\n class=\"text-destructive visible cursor-pointer sm:invisible sm:group-hover:visible\"\n title=\"{{ 'userProfile.deleteProperty' | transloco }}\"\n [attr.aria-label]=\"'userProfile.deleteProperty' | transloco\"\n [disabled]=\"\n !!pending() || !hasStoredValue('customData', key)\n \"\n (click)=\"onDeleteData('customData', key)\">\n <TrashIcon />\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n }\n </div>\n} @else if (userProfileResource.isLoading()) {\n <span>Loading...</span>\n} @else if (userProfileResource.error()) {\n <p>Please contact an administrator to create your user profile.</p>\n}\n\n<!-- AVATAR UPLOAD -->\n<input\n #avatarInput\n class=\"hidden\"\n type=\"file\"\n accept=\"image/*\"\n (change)=\"uploadAvatar($event)\" />\n" }]
|
|
18523
18604
|
}], ctorParameters: () => [], propDecorators: { createInputElement: [{ type: i0.ViewChild, args: ["avatarInput", { isSignal: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], currentLanguage: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentLanguage", required: false }] }, { type: i0.Output, args: ["currentLanguageChange"] }] } });
|
|
18524
18605
|
|
|
18525
18606
|
class UserProfileDialog {
|
|
@@ -18796,5 +18877,5 @@ const queryNameResolver = () => {
|
|
|
18796
18877
|
* Generated bundle index. Do not edit.
|
|
18797
18878
|
*/
|
|
18798
18879
|
|
|
18799
|
-
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeatureFlagsDialogComponent, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, __resetSignInMutexForTests, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, isAuthRoute, isSignInInFlight, legacyFilterToFilter, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
18880
|
+
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationItemDirective, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, EntityNavigationService, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeatureFlagsDialogComponent, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SignedOutComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, __resetSignInMutexForTests, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, isAuthRoute, isSignInInFlight, legacyFilterToFilter, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
18800
18881
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|