@sumaris-net/ngx-components 18.16.8 → 18.17.1

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.
Files changed (53) hide show
  1. package/doc/changelog.md +4 -0
  2. package/esm2022/public_api.mjs +4 -4
  3. package/esm2022/src/app/admin/users/person.filter.mjs +76 -0
  4. package/esm2022/src/app/admin/users/person.service.mjs +212 -0
  5. package/esm2022/src/app/admin/users/person.validator.mjs +72 -0
  6. package/esm2022/src/app/admin/users/users.mjs +4 -4
  7. package/esm2022/src/app/core/about/about.modal.mjs +5 -5
  8. package/esm2022/src/app/core/home/home.mjs +3 -3
  9. package/esm2022/src/app/core/services/account.service.mjs +8 -2
  10. package/esm2022/src/app/core/services/network.service.mjs +5 -2
  11. package/esm2022/src/app/core/services/validator/account.validator.mjs +2 -2
  12. package/esm2022/src/app/shared/validator/validators.mjs +3 -3
  13. package/esm2022/src/app/social/feed/feed.component.mjs +117 -30
  14. package/esm2022/src/app/social/feed/feed.model.mjs +1 -1
  15. package/esm2022/src/app/social/feed/feed.page.mjs +3 -3
  16. package/esm2022/src/app/social/feed/feed.service.mjs +63 -12
  17. package/esm2022/src/app/social/feed/testing/feed.testing.mjs +3 -3
  18. package/esm2022/src/app/social/message/message.form.mjs +8 -5
  19. package/esm2022/src/app/social/message/message.modal.mjs +10 -4
  20. package/esm2022/src/app/social/message/message.model.mjs +2 -2
  21. package/esm2022/src/app/social/message/message.module.mjs +5 -4
  22. package/esm2022/src/app/social/message/message.service.mjs +87 -1
  23. package/esm2022/src/app/social/social.errors.mjs +2 -1
  24. package/esm2022/src/environments/environment.mjs +7 -4
  25. package/fesm2022/sumaris-net.ngx-components.mjs +1310 -1048
  26. package/fesm2022/sumaris-net.ngx-components.mjs.map +1 -1
  27. package/package.json +1 -1
  28. package/public_api.d.ts +3 -3
  29. package/src/app/admin/{services/filter → users}/person.filter.d.ts +4 -4
  30. package/src/app/admin/{services → users}/person.service.d.ts +3 -1
  31. package/src/app/admin/{services/validator → users}/person.validator.d.ts +4 -4
  32. package/src/app/admin/users/users.d.ts +2 -2
  33. package/src/app/core/services/account.service.d.ts +2 -0
  34. package/src/app/core/services/network.service.d.ts +3 -2
  35. package/src/app/core/services/validator/account.validator.d.ts +1 -1
  36. package/src/app/social/feed/feed.component.d.ts +36 -14
  37. package/src/app/social/feed/feed.model.d.ts +3 -2
  38. package/src/app/social/feed/feed.service.d.ts +35 -24
  39. package/src/app/social/message/message.form.d.ts +3 -3
  40. package/src/app/social/message/message.modal.d.ts +6 -2
  41. package/src/app/social/message/message.model.d.ts +1 -1
  42. package/src/app/social/message/message.module.d.ts +2 -1
  43. package/src/app/social/message/message.service.d.ts +12 -0
  44. package/src/app/social/social.errors.d.ts +1 -0
  45. package/src/assets/i18n/en-US.json +7 -3
  46. package/src/assets/i18n/en.json +5 -2
  47. package/src/assets/i18n/fr.json +5 -2
  48. package/src/assets/manifest.json +1 -1
  49. package/src/theme/_ngx-components.forms.scss +1 -0
  50. package/src/theme/_ngx-components.table.scss +1 -0
  51. package/esm2022/src/app/admin/services/filter/person.filter.mjs +0 -76
  52. package/esm2022/src/app/admin/services/person.service.mjs +0 -184
  53. package/esm2022/src/app/admin/services/validator/person.validator.mjs +0 -72
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, Directive, Pipe, Injectable, EventEmitter, Output, Optional, Inject, inject, NgModule, forwardRef, booleanAttribute, Component, ChangeDetectionStrategy, Input, ViewChildren, HostBinding, HostListener, ElementRef, numberAttribute, ViewChild, ANIMATION_MODULE_TYPE, RendererStyleFlags2, CUSTOM_ELEMENTS_SCHEMA, ChangeDetectorRef, Self, ViewEncapsulation, APP_INITIALIZER, SecurityContext, viewChild, signal, input, model, computed, effect, untracked } from '@angular/core';
3
3
  import { firstValueFrom, shareReplay, tap, of, timer, Subject, merge, delay, isObservable, from, Subscription, BehaviorSubject, fromEvent, noop as noop$9, Observable, forkJoin, timeout, defer, combineLatest, debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1, interval, mergeMap as mergeMap$1, switchMap as switchMap$1, EMPTY } from 'rxjs';
4
- import { catchError, map, filter, takeUntil, first, switchMap, tap as tap$1, throttleTime, debounceTime, startWith, distinctUntilChanged, mergeMap, skip, finalize, bufferWhen, distinctUntilKeyChanged, take } from 'rxjs/operators';
4
+ import { catchError, map, filter, takeUntil, first, switchMap, tap as tap$1, throttleTime, debounceTime, startWith, distinctUntilChanged, mergeMap, skip, finalize, distinctUntilKeyChanged, bufferWhen, take } from 'rxjs/operators';
5
5
  import * as cloneImported from 'clone';
6
6
  import * as i3$1 from '@angular/common';
7
7
  import { CommonModule, DOCUMENT, LocationStrategy, Location, AsyncPipe, JsonPipe } from '@angular/common';
@@ -2871,14 +2871,14 @@ class SharedValidators {
2871
2871
  }
2872
2872
  static entity(control) {
2873
2873
  const value = control.value;
2874
- if (value && (typeof value !== 'object' || value.id === undefined || value.id === null)) {
2874
+ if (value && (typeof value !== 'object' || isNil(value.id))) {
2875
2875
  return { entity: true };
2876
2876
  }
2877
2877
  return null;
2878
2878
  }
2879
2879
  static entities(control) {
2880
2880
  const value = control.value;
2881
- if (value && (!Array.isArray(value) || value.every((e) => e?.id === undefined || e?.id === null))) {
2881
+ if (value && (!Array.isArray(value) || value.every((e) => isNil(e?.id)))) {
2882
2882
  return { entity: true };
2883
2883
  }
2884
2884
  return null;
@@ -13138,11 +13138,14 @@ const environment = Object.freeze({
13138
13138
  '/api/feed.json',
13139
13139
  'https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components/-/raw/master/doc/feed/feed-fr.json',
13140
13140
  // Example with JsonFeed version 1 (and not 1.1)
13141
- 'https://raw.githubusercontent.com/duniter/cesium/master/doc/feed/feed-fr.json',
13141
+ //'https://raw.githubusercontent.com/duniter/cesium/master/doc/feed/feed-fr.json',
13142
13142
  // Example with discourse API :
13143
- 'https://raw.githubusercontent.com/duniter/cesium/master/doc/feed/1.1/feed-fr-FR.json',
13143
+ //'https://raw.githubusercontent.com/duniter/cesium/master/doc/feed/1.1/feed-fr-FR.json',
13144
+ ],
13145
+ en: [
13146
+ '/api/feed.json',
13147
+ 'https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components/-/raw/master/doc/feed/feed-en.json'
13144
13148
  ],
13145
- en: ['https://gitlab.ifremer.fr/sih-public/sumaris/ngx-sumaris-components/-/raw/master/doc/feed/feed-en.json'],
13146
13149
  },
13147
13150
  maxContentLength: 1000,
13148
13151
  maxAgeInMonths: -1,
@@ -19713,6 +19716,9 @@ class NetworkService extends StartableObservableService {
19713
19716
  get connectionParams() {
19714
19717
  return this._connectionParams;
19715
19718
  }
19719
+ get peer$() {
19720
+ return this.dataSubject.pipe(distinctUntilKeyChanged('url'));
19721
+ }
19716
19722
  constructor(_document, platform, modalCtrl, storage, settings, cache, http, environment, loggingService, translate, toastController) {
19717
19723
  super(platform);
19718
19724
  this._document = _document;
@@ -23014,7 +23020,7 @@ const Fragments = {
23014
23020
  `,
23015
23021
  };
23016
23022
  // Account queries
23017
- const Queries = {
23023
+ const Queries$1 = {
23018
23024
  load: gql `
23019
23025
  query Account {
23020
23026
  data: account {
@@ -23176,12 +23182,18 @@ class AccountService extends BaseGraphqlService {
23176
23182
  get account() {
23177
23183
  return this._cache.loaded ? this._data : undefined;
23178
23184
  }
23185
+ get account$() {
23186
+ return this.onChange.asObservable().pipe(startWith(this.account));
23187
+ }
23179
23188
  get person() {
23180
23189
  if (this._cache.loaded && !this._cache.person) {
23181
23190
  this._cache.person = this._cache.loaded ? this._data.asPerson() : undefined;
23182
23191
  }
23183
23192
  return this._cache.person;
23184
23193
  }
23194
+ get person$() {
23195
+ return this.onChange.asObservable().pipe(map(() => this.person), startWith(this.person));
23196
+ }
23185
23197
  get department() {
23186
23198
  if (this._cache.loaded && !this._cache.department) {
23187
23199
  this._cache.department = this._cache.loaded ? this._data.asPerson().department : undefined;
@@ -23610,7 +23622,7 @@ class AccountService extends BaseGraphqlService {
23610
23622
  }
23611
23623
  // Load remotely
23612
23624
  else {
23613
- const query = opts?.query || (this.apiTokenEnabled ? Queries.loadWithTokens : Queries.load);
23625
+ const query = opts?.query || (this.apiTokenEnabled ? Queries$1.loadWithTokens : Queries$1.load);
23614
23626
  const { data } = await this.graphql.query({
23615
23627
  query,
23616
23628
  error: { code: ErrorCodes.LOAD_ACCOUNT_ERROR, message: 'ERROR.LOAD_ACCOUNT_ERROR' },
@@ -30020,10 +30032,10 @@ class AboutModal {
30020
30032
  }
30021
30033
  /* -- protected functions -- */
30022
30034
  setConfig(config) {
30023
- this.forumUrl = config?.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL) || this.environment.forumUrl;
30024
- this.helpUrl = config?.getProperty(CORE_CONFIG_OPTIONS.HELP_URL) || this.environment.helpUrl;
30025
- this.reportIssueUrl = config?.getProperty(CORE_CONFIG_OPTIONS.REPORT_ISSUE_URL) || this.environment.reportIssueUrl;
30026
- this.buildDate = this.environment.buildDate || new Date().toISOString();
30035
+ this.forumUrl = config?.getProperty(CORE_CONFIG_OPTIONS.FORUM_URL) || this.environment?.forumUrl;
30036
+ this.helpUrl = config?.getProperty(CORE_CONFIG_OPTIONS.HELP_URL) || this.environment?.helpUrl;
30037
+ this.reportIssueUrl = config?.getProperty(CORE_CONFIG_OPTIONS.REPORT_ISSUE_URL) || this.environment?.reportIssueUrl;
30038
+ this.buildDate = this.environment?.buildDate;
30027
30039
  }
30028
30040
  async loadNodeInfo() {
30029
30041
  this.nodeInfo = await this.getNodeInfo();
@@ -32720,6 +32732,7 @@ const SocialErrorCodes = {
32720
32732
  SAVE_USER_EVENT_ERROR: 50001,
32721
32733
  COUNT_USER_EVENT_ERROR: 50002,
32722
32734
  SEND_MESSAGE_ERROR: 50003,
32735
+ DELETE_MESSAGE_ERROR: 50004,
32723
32736
  SUBSCRIBE_USER_EVENTS_ERROR: 50005,
32724
32737
  SUBSCRIBE_JOB_PROGRESSION_ERROR: 50010,
32725
32738
  LOAD_JOB_PROGRESSIONS_ERROR: 50011,
@@ -33781,282 +33794,1068 @@ class JsonFeedUtils {
33781
33794
  }
33782
33795
  }
33783
33796
 
33784
- const APP_FEED_SERVICE = new InjectionToken('FeeService');
33785
- class FeedService extends StartableService {
33786
- settings;
33787
- environment;
33788
- _logPrefix = '[feed-service] ';
33789
- _state = new RxState();
33790
- network = inject(NetworkService);
33791
- locale$ = this._state.select('locale');
33792
- feedUrls$ = this._state.select('feedUrls');
33793
- get feedUrls() {
33794
- return this._state.get('feedUrls');
33797
+ var PersonFilter_1;
33798
+ // @dynamic
33799
+ let PersonFilter = class PersonFilter extends EntityFilter {
33800
+ static { PersonFilter_1 = this; }
33801
+ static fromObject;
33802
+ static searchFilter(source) {
33803
+ return source && PersonFilter_1.fromObject(source).asFilterFn();
33795
33804
  }
33796
- set feedUrls(urls) {
33797
- this._state.set('feedUrls', () => urls);
33805
+ email;
33806
+ pubkey;
33807
+ searchText;
33808
+ statusIds;
33809
+ userProfiles;
33810
+ includedIds;
33811
+ excludedIds;
33812
+ searchAttribute;
33813
+ searchAttributes;
33814
+ constructor() {
33815
+ super(PersonFilter_1.TYPENAME);
33798
33816
  }
33799
- constructor(settings, environment) {
33800
- super(settings);
33801
- this.settings = settings;
33802
- this.environment = environment;
33803
- this._state.connect('locale', this.settings.locale$);
33804
- this._state.connect('feedUrls', this.locale$.pipe(filter(isNotNilOrBlank), map((locale) => (locale && environment.feed?.jsonFeed?.[locale]) ?? [])));
33805
- // DEBUG
33806
- this._debug = !environment.production;
33807
- if (this._debug)
33808
- console.debug(`${this._logPrefix}created`);
33817
+ fromObject(source, opts) {
33818
+ super.fromObject(source, opts);
33819
+ this.email = source.email;
33820
+ this.pubkey = source.pubkey;
33821
+ this.searchText = source.searchText;
33822
+ this.statusIds = source.statusIds || (isNotNil(source.statusId) ? [source.statusId] : undefined);
33823
+ this.userProfiles = source.userProfiles;
33824
+ this.excludedIds = source.excludedIds;
33825
+ this.searchAttribute = source.searchAttribute;
33826
+ this.searchAttributes = source.searchAttributes;
33809
33827
  }
33810
- async ngOnStart() {
33811
- await Promise.all([this.settings.ready(), this.network.ready()]);
33812
- return {
33813
- locale: this.settings.locale,
33814
- };
33828
+ asObject(opts) {
33829
+ const target = super.asObject(opts);
33830
+ target.email = this.email;
33831
+ target.pubkey = this.pubkey;
33832
+ target.searchText = this.searchText;
33833
+ target.statusIds = this.statusIds;
33834
+ target.userProfiles = this.userProfiles;
33835
+ target.excludedIds = this.excludedIds;
33836
+ target.searchAttribute = this.searchAttribute;
33837
+ target.searchAttributes = this.searchAttributes;
33838
+ return target;
33815
33839
  }
33816
- watchAll(opts) {
33817
- if (!this.started) {
33818
- return from(this.start()).pipe(switchMap$1(() => this.watchAll(opts)));
33840
+ buildFilter() {
33841
+ const filterFns = super.buildFilter();
33842
+ // Filter by status
33843
+ if (isNotEmptyArray(this.statusIds)) {
33844
+ filterFns.push((e) => this.statusIds.includes(e.statusId));
33819
33845
  }
33820
- if (isNotEmptyArray(opts?.urls)) {
33821
- return this.locale$.pipe(mergeMap((locale) => this.loadAll(opts.urls, { locale, ...opts })));
33846
+ // Filter included ids
33847
+ const includedIds = this.includedIds;
33848
+ if (isNotEmptyArray(includedIds)) {
33849
+ filterFns.push((e) => isNotNil(e.id) && includedIds.includes(e.id));
33850
+ }
33851
+ // Filter excluded ids
33852
+ const excludedIds = this.excludedIds;
33853
+ if (isNotEmptyArray(excludedIds)) {
33854
+ filterFns.push((e) => isNil(e.id) || !excludedIds.includes(e.id));
33822
33855
  }
33823
- return this.feedUrls$.pipe(filter(isNotEmptyArray), mergeMap((urls) => this.loadAll(urls, opts)));
33856
+ // Search text
33857
+ const searchTextFilter = EntityUtils.searchTextFilter(this.searchAttribute || this.searchAttributes || ['lastName', 'firstName', 'department.name'], this.searchText);
33858
+ if (searchTextFilter)
33859
+ filterFns.push(searchTextFilter);
33860
+ return filterFns;
33824
33861
  }
33825
- getHomeUrl(feed) {
33826
- const feedUrl = feed?.feed_url ?? firstArrayValue(this.feedUrls);
33827
- return feedUrl ? UrlUtils.getRootUrl(feedUrl) : undefined;
33862
+ };
33863
+ PersonFilter = PersonFilter_1 = __decorate([
33864
+ EntityClass({ typename: 'PersonFilterVO' })
33865
+ ], PersonFilter);
33866
+
33867
+ const MessageTypes = {
33868
+ INBOX_MESSAGE: 'INBOX_MESSAGE',
33869
+ EMAIL: 'EMAIL',
33870
+ FEED: 'FEED',
33871
+ };
33872
+ const MessageTypeList = Object.freeze([
33873
+ {
33874
+ id: MessageTypes.INBOX_MESSAGE,
33875
+ icon: 'notifications',
33876
+ label: 'SOCIAL.MESSAGE.TYPE_ENUM.INBOX_MESSAGE',
33877
+ },
33878
+ {
33879
+ id: MessageTypes.EMAIL,
33880
+ icon: 'mail',
33881
+ label: 'SOCIAL.MESSAGE.TYPE_ENUM.EMAIL',
33882
+ },
33883
+ {
33884
+ id: MessageTypes.FEED,
33885
+ icon: 'megaphone',
33886
+ label: 'SOCIAL.MESSAGE.TYPE_ENUM.FEED',
33887
+ },
33888
+ ]);
33889
+ // @dynamic
33890
+ let Message = class Message extends Entity {
33891
+ static fromObject;
33892
+ type;
33893
+ issuer;
33894
+ recipients;
33895
+ recipientFilter;
33896
+ subject;
33897
+ body;
33898
+ constructor() {
33899
+ super();
33828
33900
  }
33829
- getTagUrl(feed, tag) {
33830
- if (!feed || !tag)
33831
- throw new Error("Missing 'feed' or 'tag' argument");
33832
- const baseUrl = this.getHomeUrl(feed);
33833
- return feed.tag_template?.replace('{tag}', tag) ?? baseUrl + '/tag/' + tag;
33901
+ fromObject(source, opts) {
33902
+ super.fromObject(source, opts);
33903
+ this.type = source.type;
33904
+ this.issuer = source.issuer && Person.fromObject(source.issuer);
33905
+ this.recipients = source.recipients && source.recipients.map(Person.fromObject);
33906
+ this.recipientFilter = (source.recipientFilter && PersonFilter.fromObject(source.recipientFilter)) || undefined;
33907
+ this.subject = source.subject;
33908
+ this.body = source.body;
33834
33909
  }
33835
- async load(url, opts) {
33836
- const { data } = await this.loadAll([url], opts);
33837
- return data?.[0];
33910
+ asObject(opts) {
33911
+ const target = super.asObject(opts);
33912
+ target.recipients = this.recipients && this.recipients.map((p) => p.asObject(opts));
33913
+ target.recipientFilter = (this.recipientFilter && this.recipientFilter.asObject(opts)) || undefined;
33914
+ return target;
33838
33915
  }
33839
- async loadAll(urls, opts) {
33840
- await this.ready();
33841
- urls = urls ?? this.feedUrls;
33842
- opts = {
33843
- maxAgeInMonths: opts?.maxAgeInMonths ?? this.environment.feed?.maxAgeInMonths,
33844
- maxContentLength: opts?.maxContentLength ?? this.environment.feed?.maxContentLength,
33845
- locale: this.settings.locale,
33846
- depth: 0,
33847
- ...opts,
33848
- };
33849
- const feeds = await Promise.all((urls || []).map(async (url) => {
33850
- try {
33851
- // Get JSON
33852
- const json = await this.network.get(url);
33853
- console.debug(`${this._logPrefix}Loaded JSON from ${url}`, json);
33854
- // Resolve feed
33855
- return await this.resolveFeed(json, url, opts);
33856
- }
33857
- catch (err) {
33858
- if (err?.status === 404) {
33859
- console.error(`${this._logPrefix} Error while fetching feeds at ${url}. 404 (Not found)`);
33860
- }
33861
- else {
33862
- console.error(`${this._logPrefix} Error while fetching feeds at ${url}`, err);
33863
- }
33864
- return Promise.resolve([]);
33865
- }
33866
- }));
33867
- const data = feeds.flat();
33868
- // Close opened resources
33869
- if (opts.depth === 0) {
33870
- this.onAfterLoadAll();
33871
- }
33872
- return { data, total: data.length };
33916
+ };
33917
+ Message = __decorate([
33918
+ EntityClass({ typename: 'MessageVO' })
33919
+ ], Message);
33920
+ // @dynamic
33921
+ let MessageFilter = class MessageFilter extends EntityFilter {
33922
+ static fromObject;
33923
+ fromObject(source, opts) {
33924
+ super.fromObject(source, opts);
33873
33925
  }
33874
- async resolveFeed(json, url, opts) {
33875
- // Check json is JsonFeed compatible
33876
- if (JsonFeedUtils.isJsonFeed(json)) {
33877
- // Migrate from old versions
33878
- let feed = JsonFeedUtils.migrateFeed(json);
33879
- // Resolve items (e.g. using feed.next_url)
33880
- feed = await this.resolveFeedItems(feed, { ...opts, depth: opts?.depth ?? 0 });
33881
- // Truncate html
33882
- JsonFeedUtils.truncateFeedItemsHtml(feed, opts);
33883
- return isNotEmptyArray(feed?.items) ? [feed] : [];
33884
- }
33885
- // not resolved (unknown format)
33886
- return undefined;
33926
+ buildFilter() {
33927
+ return super.buildFilter();
33887
33928
  }
33888
- async resolveFeedItems(json, opts) {
33889
- if (!json)
33890
- return json;
33891
- // Load items
33892
- if (isEmptyArray(json.items) && isNotNilOrBlank(json.next_url)) {
33893
- const depth = opts?.depth ?? 0;
33894
- if (depth >= 3) {
33895
- console.warn(`${this._logPrefix}Max depth reached (${depth}) for ${json.feed_url}`);
33896
- return json;
33897
- }
33898
- // Load items
33899
- const feed = await this.load(json.next_url, { ...opts, depth: depth + 1 });
33900
- if (isEmptyArray(feed?.items)) {
33901
- return json;
33902
- }
33903
- // Merge with the input json
33904
- return {
33905
- ...json,
33906
- title: json.title ?? feed.title,
33907
- home_page_url: json.home_page_url ?? feed.home_page_url,
33908
- authors: isNotEmptyArray(json.authors) ? json.authors : feed.authors,
33909
- items: feed.items,
33910
- tag_template: json.tag_template ?? feed.tag_template,
33911
- };
33912
- }
33913
- return json;
33929
+ };
33930
+ MessageFilter = __decorate([
33931
+ EntityClass({ typename: 'MessageFilterVO' })
33932
+ ], MessageFilter);
33933
+
33934
+ const PersonFragments = {
33935
+ lightPerson: gql$1 `
33936
+ fragment LightPersonFragment on PersonVO {
33937
+ id
33938
+ firstName
33939
+ lastName
33940
+ avatar
33941
+ department {
33942
+ id
33943
+ label
33944
+ name
33945
+ __typename
33946
+ }
33947
+ __typename
33914
33948
  }
33915
- onAfterLoadAll() {
33916
- console.debug(`${this._logPrefix}Feeds loaded`);
33949
+ `,
33950
+ person: gql$1 `
33951
+ fragment PersonFragment on PersonVO {
33952
+ id
33953
+ firstName
33954
+ lastName
33955
+ email
33956
+ pubkey
33957
+ avatar
33958
+ statusId
33959
+ updateDate
33960
+ creationDate
33961
+ profiles
33962
+ username
33963
+ usernameExtranet
33964
+ department {
33965
+ id
33966
+ label
33967
+ name
33968
+ logo
33969
+ __typename
33970
+ }
33971
+ __typename
33917
33972
  }
33918
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedService, deps: [{ token: LocalSettingsService }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
33919
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedService });
33920
- }
33921
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedService, decorators: [{
33922
- type: Injectable
33923
- }], ctorParameters: () => [{ type: LocalSettingsService }, { type: Environment, decorators: [{
33924
- type: Optional
33925
- }, {
33926
- type: Inject,
33927
- args: [ENVIRONMENT]
33928
- }] }] });
33929
-
33930
- class FeedDirective {
33931
- element;
33932
- _subscription = new Subscription();
33933
- platform = inject(PlatformService);
33934
- locationStrategy = inject(LocationStrategy);
33935
- navController = inject(NavController);
33936
- router = inject(Router);
33937
- route = inject(ActivatedRoute);
33938
- _feedUrl;
33939
- _baseUrl;
33940
- set feedUrl(value) {
33941
- this._feedUrl = value;
33942
- this._baseUrl = value && JsonFeedUtils.removeJsonExtension(value);
33973
+ `,
33974
+ personFilter: gql$1 `
33975
+ fragment PersonFilterFragment on PersonFilterVO {
33976
+ email
33977
+ pubkey
33978
+ searchText
33979
+ statusIds
33980
+ userProfiles
33981
+ includedIds
33982
+ excludedIds
33983
+ searchAttribute
33984
+ searchAttributes
33943
33985
  }
33944
- get feedUrl() {
33945
- return this._feedUrl;
33986
+ `,
33987
+ };
33988
+ // Load persons query
33989
+ const PersonQueries = {
33990
+ loadAll: gql$1 `
33991
+ query Persons($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: PersonFilterVOInput) {
33992
+ data: persons(filter: $filter, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection) {
33993
+ ...PersonFragment
33994
+ }
33946
33995
  }
33947
- constructor(element) {
33948
- this.element = element;
33949
- // DEBUG
33950
- //console.debug('[feed-directive] Creating feed directive');
33996
+ ${PersonFragments.person}
33997
+ `,
33998
+ loadAllWithTotal: gql$1 `
33999
+ query PersonsWithTotal($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: PersonFilterVOInput) {
34000
+ data: persons(filter: $filter, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection) {
34001
+ ...PersonFragment
34002
+ }
34003
+ total: personsCount(filter: $filter)
33951
34004
  }
33952
- ngAfterViewInit() {
33953
- console.debug('[feed-directive] Processing anchors');
33954
- // Listening click events
33955
- this._subscription.add(this.listenClickEvents(this.element.nativeElement));
34005
+ ${PersonFragments.person}
34006
+ `,
34007
+ };
34008
+ const PersonMutations = {
34009
+ saveAll: gql$1 `
34010
+ mutation savePersons($data: [PersonVOInput]) {
34011
+ data: savePersons(persons: $data) {
34012
+ ...PersonFragment
34013
+ }
33956
34014
  }
33957
- ngOnDestroy() {
33958
- this._subscription.unsubscribe();
34015
+ ${PersonFragments.person}
34016
+ `,
34017
+ deleteAll: gql$1 `
34018
+ mutation deletePersons($ids: [Int]) {
34019
+ deletePersons(ids: $ids)
33959
34020
  }
33960
- listenClickEvents(nativeElement) {
33961
- console.debug('[feed-directive] Start listening click events...');
33962
- const subscription = new Subscription();
33963
- const listener = (event) => this.click(event, nativeElement);
33964
- const links = nativeElement.querySelectorAll('a');
33965
- links.forEach((link) => {
33966
- // DEBUG
33967
- //console.debug('[feed-directive] Adding click listener to', link);
33968
- link.addEventListener('click', listener);
33969
- subscription.add(() => link.removeEventListener('click', listener));
34021
+ `,
34022
+ };
34023
+ class PersonService extends BaseEntityService {
34024
+ graphql;
34025
+ platform;
34026
+ network;
34027
+ entities;
34028
+ constructor(graphql, platform, network, entities) {
34029
+ super(graphql, platform, Person, PersonFilter, {
34030
+ queries: PersonQueries,
34031
+ mutations: PersonMutations,
34032
+ defaultSortBy: 'lastName',
34033
+ defaultSortDirection: 'asc',
33970
34034
  });
33971
- // DEBUG
33972
- subscription.add(() => console.debug('[feed-directive] Stop listening click events'));
33973
- return subscription;
34035
+ this.graphql = graphql;
34036
+ this.platform = platform;
34037
+ this.network = network;
34038
+ this.entities = entities;
34039
+ // for DEV only -----
34040
+ this._debug = !environment.production;
33974
34041
  }
33975
- async click(event, containerElement) {
33976
- console.debug('[feed-directive] Processing click event...', event);
33977
- if (!(event.target instanceof HTMLAnchorElement) && !(event.target?.['tagName'] === 'A')) {
33978
- console.warn('[feed-directive] Invalid click event target. Should an anchor <a>', event.target);
33979
- return;
33980
- }
33981
- const element = event.target;
33982
- let href = element.getAttribute('href');
33983
- const fragment = UrlUtils.getFragment(href);
33984
- // Fragment
33985
- if (href?.startsWith('#') && isNotNilOrBlank(fragment)) {
33986
- event.preventDefault();
33987
- this.scrollToAnchor(containerElement, fragment);
33988
- return true;
33989
- }
33990
- const routerLink = element.getAttribute('routerLink');
33991
- if (isNotNilOrBlank(routerLink)) {
33992
- event.preventDefault();
33993
- return this.navController.navigateForward(routerLink);
33994
- }
33995
- // Resolve relative URL, if baseUrl has been set
33996
- if (this._baseUrl && UrlUtils.isRelativeUrl(href)) {
33997
- // Resolve URL, then open using the platform
33998
- href = UrlUtils.resolveRelativeUrl(this._baseUrl, href);
33999
- }
34000
- // Resolve internal URL as an app route
34001
- if (UrlUtils.isInternalUrl(href)) {
34002
- const routePath = this.normalizeUrl(href.startsWith('/') ? href : `/${href}`);
34003
- const urlTree = this.getUrlTree(routePath);
34004
- event.preventDefault();
34005
- this.router.navigated = false;
34006
- // Opening route
34007
- return this.navController.navigateForward(urlTree);
34042
+ async loadAll(offset, size, sortBy, sortDirection, filter, opts) {
34043
+ const offline = this.network.offline && (!opts || opts.fetchPolicy !== 'network-only');
34044
+ if (offline) {
34045
+ return this.loadAllLocally(offset, size, sortBy, sortDirection, filter, opts);
34008
34046
  }
34009
- // External URL: open using the platform
34010
- event.preventDefault();
34011
- await this.platform.open(href);
34012
- return true;
34047
+ return super.loadAll(offset, size, sortBy, sortDirection, filter, opts);
34013
34048
  }
34014
- scrollToAnchor(containerElement, anchorId) {
34015
- if (!containerElement || !anchorId) {
34016
- console.warn("Missing 'containerElement' or 'fragment' argument.");
34017
- return;
34018
- }
34019
- console.debug('[feed-directive-anchor] Scrolling to anchor #' + anchorId);
34020
- // Find the target element with the specified fragment ID
34021
- const targetElement = containerElement.querySelector(`#${anchorId}`);
34022
- if (targetElement) {
34023
- // Scroll smoothly to the target element
34024
- // eslint-disable-next-line @rx-angular/prefer-no-layout-sensitive-apis
34025
- targetElement.scrollIntoView({
34026
- behavior: 'smooth', // Smooth scrolling behavior
34027
- block: 'start', // Align the target element to the top of the container
34028
- });
34029
- }
34030
- else {
34031
- console.warn(`No element found with the ID "${anchorId}" inside the container.`);
34049
+ async loadAllLocally(offset, size, sortBy, sortDirection, filter, opts) {
34050
+ filter = this.asFilter(filter);
34051
+ const variables = {
34052
+ offset: offset || 0,
34053
+ size: size || 100,
34054
+ sortBy: sortBy || this.defaultSortBy,
34055
+ sortDirection: sortDirection || this.defaultSortDirection,
34056
+ filter: filter && filter.asFilterFn(),
34057
+ };
34058
+ const { data, total } = await this.entities.loadAll('PersonVO', variables);
34059
+ const entities = this.fromObjects(data, opts);
34060
+ const res = { data: entities, total };
34061
+ // Add fetch more function
34062
+ const nextOffset = (offset || 0) + entities.length;
34063
+ if (nextOffset < total) {
34064
+ res.fetchMore = () => this.loadAllLocally(nextOffset, size, sortBy, sortDirection, filter, opts);
34032
34065
  }
34066
+ return res;
34033
34067
  }
34034
- /**
34035
- * Transform a relative URL to its absolute representation according to current router state.
34036
- * @param url Relative URL path.
34037
- * @return Absolute URL based on the current route.
34038
- */
34039
- normalizeUrl(url) {
34040
- if (UrlUtils.isExternalUrl(url)) {
34041
- return url;
34042
- }
34043
- if (this._baseUrl && UrlUtils.isRelativeUrl(url)) {
34044
- return UrlUtils.resolveRelativeUrl(this._baseUrl, url);
34045
- }
34046
- const urlTree = this.getUrlTree(url);
34047
- const serializedUrl = this.router.serializeUrl(urlTree);
34048
- return this.locationStrategy.prepareExternalUrl(serializedUrl);
34068
+ async suggest(value, filter, sortBy, sortDirection, opts) {
34069
+ if (EntityUtils.isNotEmpty(value, 'id'))
34070
+ return { data: [value] };
34071
+ value = (typeof value === 'string' && value !== '*' && value) || undefined;
34072
+ sortBy = sortBy || filter.searchAttribute || (filter.searchAttributes && filter.searchAttributes[0]);
34073
+ return this.loadAll(0, !value ? 30 : 10, sortBy, sortDirection, {
34074
+ ...filter,
34075
+ searchText: value,
34076
+ statusIds: (filter && filter.statusIds) || [StatusIds.ENABLE, StatusIds.TEMPORARY],
34077
+ userProfiles: filter && filter.userProfiles,
34078
+ }, {
34079
+ withTotal: true /* need by autocomplete */,
34080
+ ...opts,
34081
+ });
34049
34082
  }
34050
- getUrlTree(url) {
34051
- url = UrlUtils.normalizeUrl(url);
34052
- const urlPath = UrlUtils.stripFragmentAndQuery(url) || UrlUtils.stripFragmentAndQuery(this.router.url);
34053
- const parsedUrl = this.router.parseUrl(url);
34054
- const fragment = parsedUrl.fragment;
34055
- const queryParams = parsedUrl.queryParams;
34056
- return this.router.createUrlTree([urlPath], { relativeTo: this.route, fragment, queryParams });
34083
+ async executeImport(filter, opts) {
34084
+ const maxProgression = (opts && opts.maxProgression) || 100;
34085
+ filter = {
34086
+ ...filter,
34087
+ statusIds: [StatusIds.ENABLE, StatusIds.TEMPORARY],
34088
+ userProfiles: ['SUPERVISOR', 'USER', 'GUEST'],
34089
+ };
34090
+ console.info('[person-service] Importing persons...');
34091
+ const res = await JobUtils.fetchAllPages((offset, size) => this.loadAll(offset, size, 'id', null, filter, {
34092
+ debug: false,
34093
+ fetchPolicy: 'network-only',
34094
+ withTotal: offset === 0, // Compute total only once
34095
+ toEntity: false,
34096
+ }), { progression: opts?.progression, maxProgression: maxProgression * 0.9 });
34097
+ // Save result locally
34098
+ await this.entities.saveAll(res.data, { entityName: 'PersonVO', reset: true });
34057
34099
  }
34058
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
34059
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FeedDirective, selector: "feed,[feed]", inputs: { feedUrl: ["feed", "feedUrl"] }, ngImport: i0 });
34100
+ async loadById(id, opts) {
34101
+ const { data } = await this.loadAll(0, 1, null, null, { includedIds: [id] }, { withTotal: false, ...opts });
34102
+ const source = isNotEmptyArray(data) ? data[0] : { id };
34103
+ return this.fromObject(source, opts);
34104
+ }
34105
+ async loadByPubkey(pubkey, opts) {
34106
+ const { data } = await this.loadAll(0, 1, null, null, { pubkey }, { withTotal: false, ...opts });
34107
+ const source = isNotEmptyArray(data) ? data[0] : { pubkey };
34108
+ return this.fromObject(source, opts);
34109
+ }
34110
+ /* -- protected methods -- */
34111
+ asObject(source) {
34112
+ if (!source)
34113
+ return undefined;
34114
+ if (!(source instanceof Person)) {
34115
+ source = Person.fromObject(source);
34116
+ }
34117
+ const target = source.asObject();
34118
+ // Not known in server GraphQL schema
34119
+ delete target.mainProfile;
34120
+ target.department = source.department?.asObject();
34121
+ return target;
34122
+ }
34123
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PersonService, deps: [{ token: GraphqlService }, { token: PlatformService }, { token: NetworkService }, { token: EntitiesStorage }], target: i0.ɵɵFactoryTarget.Injectable });
34124
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PersonService, providedIn: 'root' });
34125
+ }
34126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PersonService, decorators: [{
34127
+ type: Injectable,
34128
+ args: [{ providedIn: 'root' }]
34129
+ }], ctorParameters: () => [{ type: GraphqlService }, { type: PlatformService }, { type: NetworkService }, { type: EntitiesStorage }] });
34130
+
34131
+ class MessageForm extends AppForm {
34132
+ formBuilder;
34133
+ cd;
34134
+ mobile;
34135
+ suggestFn;
34136
+ subjectMinLength = 5;
34137
+ subjectMaxLength = 255;
34138
+ bodyMaxLength = 2000;
34139
+ bodyAutoHeight = true;
34140
+ canSelectType = false;
34141
+ canRecipientFilter = false;
34142
+ recipientFilterCount = 0;
34143
+ types = MessageTypeList;
34144
+ constructor(injector, formBuilder, cd) {
34145
+ super(injector);
34146
+ this.formBuilder = formBuilder;
34147
+ this.cd = cd;
34148
+ this.mobile = this.settings.mobile;
34149
+ }
34150
+ ngOnInit() {
34151
+ this.setForm(this.formBuilder.group({
34152
+ type: [MessageTypes.INBOX_MESSAGE, Validators.required],
34153
+ recipients: [null, Validators.required],
34154
+ recipientFilter: [null],
34155
+ subject: [
34156
+ null,
34157
+ this.subjectMaxLength
34158
+ ? Validators.compose([Validators.required, Validators.minLength(this.subjectMinLength), Validators.maxLength(this.subjectMaxLength)])
34159
+ : Validators.required,
34160
+ ],
34161
+ body: [null, this.bodyMaxLength ? Validators.compose([Validators.maxLength(this.bodyMaxLength)]) : Validators.required],
34162
+ }));
34163
+ this.registerSubscription(this._form
34164
+ .get('type')
34165
+ .valueChanges.pipe(filter(isNotNil))
34166
+ .subscribe((type) => this.updateFormGroup(this._form, { type })));
34167
+ // Person combo
34168
+ const personAttributes = this.settings.getFieldDisplayAttributes('person', ['lastName', 'firstName', 'department.name']);
34169
+ this.registerAutocompleteField('recipients', {
34170
+ showAllOnFocus: false,
34171
+ suggestFn: this.suggestFn,
34172
+ filter: {
34173
+ statusIds: [StatusIds.TEMPORARY, StatusIds.ENABLE],
34174
+ },
34175
+ attributes: personAttributes,
34176
+ columnNames: personAttributes.map((attr) => `USER.${changeCaseToUnderscore(attr).toUpperCase()}`),
34177
+ displayWith: PersonUtils.personToString,
34178
+ multiple: true,
34179
+ mobile: this.mobile,
34180
+ });
34181
+ }
34182
+ isSamePerson(o1, o2) {
34183
+ return EntityUtils.equals(o1, o2, 'id');
34184
+ }
34185
+ updateFormGroup(formGroup, opts) {
34186
+ console.debug('[message-form] Updating form group...', opts);
34187
+ // Recipient validator
34188
+ const recipientsRequired = toBoolean(opts?.recipientRequired, opts?.type !== MessageTypes.FEED);
34189
+ {
34190
+ const control = formGroup.get('recipients');
34191
+ if (recipientsRequired) {
34192
+ if (!control.hasValidator(Validators.required)) {
34193
+ control.addValidators(Validators.required);
34194
+ }
34195
+ control.enable();
34196
+ }
34197
+ else {
34198
+ if (control.hasValidator(Validators.required)) {
34199
+ control.removeValidators(Validators.required);
34200
+ }
34201
+ control.disable();
34202
+ }
34203
+ }
34204
+ // Recipient filter validator
34205
+ const recipientFilterRequired = this.canRecipientFilter && !recipientsRequired;
34206
+ {
34207
+ const control = formGroup.get('recipientFilter');
34208
+ if (recipientFilterRequired) {
34209
+ if (!control.hasValidator(Validators.required)) {
34210
+ control.addValidators(Validators.required);
34211
+ }
34212
+ control.enable();
34213
+ }
34214
+ else {
34215
+ if (control.hasValidator(Validators.required)) {
34216
+ control.removeValidators(Validators.required);
34217
+ }
34218
+ control.disable();
34219
+ }
34220
+ }
34221
+ formGroup.updateValueAndValidity();
34222
+ }
34223
+ markForCheck() {
34224
+ this.cd.markForCheck();
34225
+ }
34226
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageForm, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
34227
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: MessageForm, selector: "app-message-form", inputs: { mobile: "mobile", suggestFn: "suggestFn", subjectMinLength: "subjectMinLength", subjectMaxLength: "subjectMaxLength", bodyMaxLength: "bodyMaxLength", bodyAutoHeight: "bodyAutoHeight", canSelectType: "canSelectType", canRecipientFilter: "canRecipientFilter", recipientFilterCount: "recipientFilterCount" }, usesInheritance: true, ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <ng-container *ngTemplateOutlet=\"debugPanel\"></ng-container>\n}\n\n<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n <mat-form-field *ngIf=\"canSelectType\">\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n ></mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n <mat-error *ngIf=\"form.controls.subject.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('minlength')\">\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-hint *ngIf=\"subjectMaxLength\" align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n </mat-form-field>\n\n <!-- Body -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n <mat-error *ngIf=\"form.controls.body.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n <mat-hint *ngIf=\"bodyMaxLength\" align=\"end\">\n {{ 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength } }}\n </mat-hint>\n </mat-form-field>\n</form>\n\n\n<ng-template #debugPanel>\n <app-debug title=\"Message form\">\n <ion-grid>\n <ion-row>\n <ion-col>\n ready: {{ readySubject | async }}\n <br />\n loading: {{ loadingSubject | async }}\n <br />\n enabled: {{ enabled }}\n <br />\n dirty: {{ dirty }}\n <br />\n valid: {{ valid }}\n <br />\n <br />\n error: {{ _form | formError | json}}\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n</ng-template>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "directive", type: i1$4.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: MatChipsField, selector: "mat-chips-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "placeholder", "suggestFn", "required", "mobile", "readonly", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "itemSize", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "chipColor", "debug", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "hideRequiredMarker", "colSizes", "separatorKeysCodes", "appearance", "subscriptSizing", "class", "filter", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: AutoResizeDirective, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMaxRows", "cdkAutosizeMinRows"] }, { kind: "component", type: i11$1.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "component", type: i11$1.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: FormErrorPipe, name: "formError" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34228
+ }
34229
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageForm, decorators: [{
34230
+ type: Component,
34231
+ args: [{ selector: 'app-message-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <ng-container *ngTemplateOutlet=\"debugPanel\"></ng-container>\n}\n\n<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n <mat-form-field *ngIf=\"canSelectType\">\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n ></mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n <mat-error *ngIf=\"form.controls.subject.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('minlength')\">\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-hint *ngIf=\"subjectMaxLength\" align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n </mat-form-field>\n\n <!-- Body -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n <mat-error *ngIf=\"form.controls.body.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n <mat-hint *ngIf=\"bodyMaxLength\" align=\"end\">\n {{ 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength } }}\n </mat-hint>\n </mat-form-field>\n</form>\n\n\n<ng-template #debugPanel>\n <app-debug title=\"Message form\">\n <ion-grid>\n <ion-row>\n <ion-col>\n ready: {{ readySubject | async }}\n <br />\n loading: {{ loadingSubject | async }}\n <br />\n enabled: {{ enabled }}\n <br />\n dirty: {{ dirty }}\n <br />\n valid: {{ valid }}\n <br />\n <br />\n error: {{ _form | formError | json}}\n </ion-col>\n </ion-row>\n </ion-grid>\n </app-debug>\n</ng-template>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}\n"] }]
34232
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: i0.ChangeDetectorRef }], propDecorators: { mobile: [{
34233
+ type: Input
34234
+ }], suggestFn: [{
34235
+ type: Input
34236
+ }], subjectMinLength: [{
34237
+ type: Input
34238
+ }], subjectMaxLength: [{
34239
+ type: Input
34240
+ }], bodyMaxLength: [{
34241
+ type: Input
34242
+ }], bodyAutoHeight: [{
34243
+ type: Input
34244
+ }], canSelectType: [{
34245
+ type: Input
34246
+ }], canRecipientFilter: [{
34247
+ type: Input
34248
+ }], recipientFilterCount: [{
34249
+ type: Input
34250
+ }] } });
34251
+
34252
+ class MessageModal {
34253
+ settings;
34254
+ viewCtrl;
34255
+ accountService;
34256
+ cd;
34257
+ mobile;
34258
+ debug = false;
34259
+ title = 'SOCIAL.MESSAGE.NEW';
34260
+ suggestFn;
34261
+ data;
34262
+ canSelectType;
34263
+ canRecipientFilter;
34264
+ recipientFilterCount;
34265
+ form;
34266
+ constructor(settings, viewCtrl, accountService, cd) {
34267
+ this.settings = settings;
34268
+ this.viewCtrl = viewCtrl;
34269
+ this.accountService = accountService;
34270
+ this.cd = cd;
34271
+ this.mobile = this.settings.mobile;
34272
+ }
34273
+ ngOnInit() {
34274
+ this.canSelectType = toBoolean(this.canSelectType, this.accountService.isAdmin());
34275
+ }
34276
+ ngAfterViewInit() {
34277
+ setTimeout(() => {
34278
+ this.form.markAsReady({ emitEvent: false });
34279
+ if (this.data) {
34280
+ this.data.type = this.data.type || MessageTypes.INBOX_MESSAGE;
34281
+ this.form.setValue(this.data);
34282
+ }
34283
+ this.form.markAsLoaded();
34284
+ this.form.enable();
34285
+ });
34286
+ }
34287
+ cancel() {
34288
+ this.viewCtrl.dismiss();
34289
+ }
34290
+ async doSubmit() {
34291
+ if (this.form.disabled)
34292
+ return;
34293
+ if (!this.form.valid) {
34294
+ await AppFormUtils.waitWhilePending(this.form);
34295
+ if (this.form.invalid) {
34296
+ AppFormUtils.logFormErrors(this.form.form, '[message-modal] ');
34297
+ this.form.markAllAsTouched();
34298
+ return;
34299
+ }
34300
+ }
34301
+ this.markAsLoading();
34302
+ try {
34303
+ const data = this.form.value;
34304
+ // Disable the form
34305
+ this.form.disable();
34306
+ const entity = Message.fromObject(data);
34307
+ return this.viewCtrl.dismiss(entity);
34308
+ }
34309
+ catch (err) {
34310
+ this.form.error = (err && err.message) || err;
34311
+ this.markAsLoaded();
34312
+ // Enable the form
34313
+ this.form.enable();
34314
+ // Reset form error on next changes
34315
+ firstNotNilPromise(this.form.form.valueChanges).then(() => {
34316
+ this.form.error = null;
34317
+ this.markForCheck();
34318
+ });
34319
+ return;
34320
+ }
34321
+ }
34322
+ markForCheck() {
34323
+ this.cd.markForCheck();
34324
+ }
34325
+ markAsLoading(opts) {
34326
+ this.form.markAsLoading(opts);
34327
+ }
34328
+ markAsLoaded(opts) {
34329
+ this.form.markAsLoaded(opts);
34330
+ }
34331
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModal, deps: [{ token: LocalSettingsService }, { token: i2$1.ModalController }, { token: AccountService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
34332
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: MessageModal, selector: "app-message-modal", inputs: { debug: "debug", title: "title", suggestFn: "suggestFn", data: "data", canSelectType: "canSelectType", canRecipientFilter: "canRecipientFilter", recipientFilterCount: "recipientFilterCount" }, viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, static: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ title | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-item *ngIf=\"form.error\" visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"form.error | translate\"></ion-label>\n </ion-item>\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n [debug]=\"debug\">\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-label translate>COMMON.BTN_SEND</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: MessageForm, selector: "app-message-form", inputs: ["mobile", "suggestFn", "subjectMinLength", "subjectMaxLength", "bodyMaxLength", "bodyAutoHeight", "canSelectType", "canRecipientFilter", "recipientFilterCount"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34333
+ }
34334
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModal, decorators: [{
34335
+ type: Component,
34336
+ args: [{ selector: 'app-message-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ title | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-item *ngIf=\"form.error\" visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"form.error | translate\"></ion-label>\n </ion-item>\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n [debug]=\"debug\">\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-label translate>COMMON.BTN_SEND</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n" }]
34337
+ }], ctorParameters: () => [{ type: LocalSettingsService }, { type: i2$1.ModalController }, { type: AccountService }, { type: i0.ChangeDetectorRef }], propDecorators: { debug: [{
34338
+ type: Input
34339
+ }], title: [{
34340
+ type: Input
34341
+ }], suggestFn: [{
34342
+ type: Input
34343
+ }], data: [{
34344
+ type: Input
34345
+ }], canSelectType: [{
34346
+ type: Input
34347
+ }], canRecipientFilter: [{
34348
+ type: Input
34349
+ }], recipientFilterCount: [{
34350
+ type: Input
34351
+ }], form: [{
34352
+ type: ViewChild,
34353
+ args: ['form', { static: true }]
34354
+ }] } });
34355
+
34356
+ const MessageFragments = {
34357
+ message: gql$1 `
34358
+ fragment MessageFragment on MessageVO {
34359
+ id
34360
+ subject
34361
+ body
34362
+ type
34363
+ recipient {
34364
+ ...LightPersonFragment
34365
+ }
34366
+ recipients {
34367
+ ...LightPersonFragment
34368
+ }
34369
+ recipientFilter {
34370
+ ...PersonFilterFragment
34371
+ }
34372
+ issuer {
34373
+ ...LightPersonFragment
34374
+ }
34375
+ }
34376
+ `,
34377
+ };
34378
+ const Queries = {
34379
+ load: gql$1 `
34380
+ mutation LoadMessage($id: Int!) {
34381
+ data: message(id: $id) {
34382
+ ...MessageFragment
34383
+ }
34384
+ }
34385
+ ${MessageFragments.message}
34386
+ ${PersonFragments.lightPerson}
34387
+ ${PersonFragments.personFilter}
34388
+ `,
34389
+ };
34390
+ const Mutations = {
34391
+ send: gql$1 `
34392
+ mutation SendMessage($data: MessageVOInput) {
34393
+ done: sendMessage(message: $data)
34394
+ }
34395
+ `,
34396
+ delete: gql$1 `
34397
+ mutation DeleteMessage($id: Int!) {
34398
+ done: deleteMessage(id: $id)
34399
+ }
34400
+ `,
34401
+ };
34402
+ class MessageService extends BaseGraphqlService {
34403
+ graphql;
34404
+ translate;
34405
+ modalCtrl;
34406
+ toastController;
34407
+ environment;
34408
+ constructor(graphql, translate, modalCtrl, toastController, environment) {
34409
+ super(graphql, environment);
34410
+ this.graphql = graphql;
34411
+ this.translate = translate;
34412
+ this.modalCtrl = modalCtrl;
34413
+ this.toastController = toastController;
34414
+ this.environment = environment;
34415
+ // For DEV only
34416
+ this._debug = !environment.production;
34417
+ }
34418
+ /**
34419
+ * Send a message to recipient(s)
34420
+ *
34421
+ * @param entity
34422
+ * @param opts
34423
+ */
34424
+ async send(entity, opts) {
34425
+ // Transform into json
34426
+ const data = entity.asObject(MINIFY_ENTITY_FOR_POD);
34427
+ const now = Date.now();
34428
+ if (this._debug)
34429
+ console.debug(`[message-service] Sending message...`);
34430
+ try {
34431
+ const { done } = await this.graphql.mutate({
34432
+ mutation: Mutations.send,
34433
+ variables: { data },
34434
+ error: { code: SocialErrorCodes.SEND_MESSAGE_ERROR, message: 'SOCIAL.ERROR.SEND_MESSAGE_ERROR' },
34435
+ });
34436
+ if (this._debug)
34437
+ console.debug(`[message-service] Send message [OK] in ${Date.now() - now}ms`);
34438
+ if (done && (!opts || opts.showToast !== false)) {
34439
+ await this.showToast({ type: 'info', message: 'SOCIAL.INFO.MESSAGE_SENT' });
34440
+ }
34441
+ return done;
34442
+ }
34443
+ catch (err) {
34444
+ const error = (err && err.message) || err;
34445
+ console.error(error);
34446
+ // Show error
34447
+ if (!opts || opts.showToast !== false) {
34448
+ const message = error || 'SOCIAL.ERROR.SEND_MESSAGE_ERROR';
34449
+ await this.showToast({ type: 'error', message });
34450
+ }
34451
+ return false;
34452
+ }
34453
+ }
34454
+ /**
34455
+ * Delete a message by ID
34456
+ *
34457
+ * @param id
34458
+ * @param opts
34459
+ */
34460
+ async delete(id, opts) {
34461
+ const now = Date.now();
34462
+ if (this._debug)
34463
+ console.debug(`[message-service] Deleting message #${id}...`);
34464
+ try {
34465
+ const { done } = await this.graphql.mutate({
34466
+ mutation: Mutations.delete,
34467
+ variables: { id },
34468
+ error: { code: SocialErrorCodes.DELETE_MESSAGE_ERROR, message: 'SOCIAL.ERROR.DELETE_MESSAGE_ERROR' },
34469
+ });
34470
+ if (this._debug)
34471
+ console.debug(`[message-service] Delete message [OK] in ${Date.now() - now}ms`);
34472
+ if (done && (!opts || opts.showToast !== false)) {
34473
+ await this.showToast({ type: 'info', message: 'SOCIAL.INFO.MESSAGE_DELETED' });
34474
+ }
34475
+ return done;
34476
+ }
34477
+ catch (err) {
34478
+ const error = (err && err.message) || err;
34479
+ console.error(error);
34480
+ // Show error
34481
+ if (!opts || opts.showToast !== false) {
34482
+ const message = error || 'SOCIAL.ERROR.DELETE_MESSAGE_ERROR';
34483
+ await this.showToast({ type: 'error', message });
34484
+ }
34485
+ return false;
34486
+ }
34487
+ }
34488
+ async openComposeModal(options) {
34489
+ const hasTopModal = !!(await this.modalCtrl.getTop());
34490
+ const modal = await this.modalCtrl.create({
34491
+ component: MessageModal,
34492
+ componentProps: options,
34493
+ cssClass: hasTopModal && 'stack-modal',
34494
+ });
34495
+ // Open the modal
34496
+ await modal.present();
34497
+ // On dismiss
34498
+ const { data } = await modal.onDidDismiss();
34499
+ if (!data || !(data instanceof Message))
34500
+ return true; // CANCELLED
34501
+ // Send message
34502
+ return await this.send(data, { showToast: options?.showToast });
34503
+ }
34504
+ async load(id, opts) {
34505
+ const { data } = await this.graphql.query({
34506
+ query: Queries.load,
34507
+ variables: {
34508
+ id,
34509
+ },
34510
+ fetchPolicy: 'no-cache',
34511
+ });
34512
+ const entity = opts?.toEntity !== false ? Message.fromObject(data) : data;
34513
+ console.debug(`[message-service] Loaded message #${id}:`, entity);
34514
+ return entity;
34515
+ }
34516
+ /* -- protected methods -- */
34517
+ async showToast(opts) {
34518
+ return Toasts.show(this.toastController, this.translate, {
34519
+ type: 'info',
34520
+ message: 'SOCIAL.INFO.MESSAGE_SENT',
34521
+ ...opts,
34522
+ });
34523
+ }
34524
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageService, deps: [{ token: GraphqlService }, { token: i1$1.TranslateService }, { token: i2$1.ModalController }, { token: i2$1.ToastController }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
34525
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageService, providedIn: 'root' });
34526
+ }
34527
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageService, decorators: [{
34528
+ type: Injectable,
34529
+ args: [{ providedIn: 'root' }]
34530
+ }], ctorParameters: () => [{ type: GraphqlService }, { type: i1$1.TranslateService }, { type: i2$1.ModalController }, { type: i2$1.ToastController }, { type: Environment, decorators: [{
34531
+ type: Optional
34532
+ }, {
34533
+ type: Inject,
34534
+ args: [ENVIRONMENT]
34535
+ }] }] });
34536
+
34537
+ const APP_FEED_SERVICE = new InjectionToken('FeeService');
34538
+ class FeedService extends StartableService {
34539
+ settings;
34540
+ environment;
34541
+ _logPrefix = '[feed-service] ';
34542
+ _state = new RxState();
34543
+ network = inject(NetworkService);
34544
+ modalCtrl = inject(ModalController);
34545
+ messageService = inject(MessageService);
34546
+ personService = inject(PersonService);
34547
+ locale$ = this._state.select('locale');
34548
+ feedUrls$ = this._state.select('feedUrls');
34549
+ get feedUrls() {
34550
+ return this._state.get('feedUrls');
34551
+ }
34552
+ set feedUrls(urls) {
34553
+ this._state.set('feedUrls', () => urls);
34554
+ }
34555
+ constructor(settings, environment) {
34556
+ super(settings);
34557
+ this.settings = settings;
34558
+ this.environment = environment;
34559
+ this._state.connect('locale', this.settings.locale$);
34560
+ this._state.connect('feedUrls', this.locale$.pipe(filter(isNotNilOrBlank), map((locale) => (locale && environment.feed?.jsonFeed?.[locale]) ?? [])));
34561
+ // DEBUG
34562
+ this._debug = !environment.production;
34563
+ if (this._debug)
34564
+ console.debug(`${this._logPrefix}created`);
34565
+ }
34566
+ async ngOnStart() {
34567
+ await Promise.all([this.settings.ready(), this.network.ready()]);
34568
+ return {
34569
+ locale: this.settings.locale,
34570
+ };
34571
+ }
34572
+ watchAll(opts) {
34573
+ if (!this.started) {
34574
+ return from(this.start()).pipe(switchMap$1(() => this.watchAll(opts)));
34575
+ }
34576
+ if (isNotEmptyArray(opts?.urls)) {
34577
+ const locale$ = opts.locale ? of(opts.locale) : this.locale$;
34578
+ return locale$.pipe(mergeMap((locale) => this.loadAll({ locale, ...opts })));
34579
+ }
34580
+ return this.feedUrls$.pipe(filter(isNotEmptyArray), mergeMap((urls) => this.loadAll({ ...opts, urls })));
34581
+ }
34582
+ getHomeUrl(feed) {
34583
+ const feedUrl = feed?.feed_url ?? firstArrayValue(this.feedUrls);
34584
+ return feedUrl ? UrlUtils.getRootUrl(feedUrl) : undefined;
34585
+ }
34586
+ getTagUrl(feed, tag) {
34587
+ if (!feed || !tag)
34588
+ throw new Error("Missing 'feed' or 'tag' argument");
34589
+ const baseUrl = this.getHomeUrl(feed);
34590
+ return feed.tag_template?.replace('{tag}', tag) ?? baseUrl + '/tag/' + tag;
34591
+ }
34592
+ async load(url, opts) {
34593
+ if (!url)
34594
+ throw new Error('Missing url argument');
34595
+ const { data } = await this.loadAll({ ...opts, urls: [url] });
34596
+ return firstArrayValue(data);
34597
+ }
34598
+ async loadAll(opts) {
34599
+ await this.ready();
34600
+ const urls = opts?.urls ?? this.feedUrls;
34601
+ opts = {
34602
+ maxAgeInMonths: opts?.maxAgeInMonths ?? this.environment.feed?.maxAgeInMonths,
34603
+ maxContentLength: opts?.maxContentLength ?? this.environment.feed?.maxContentLength,
34604
+ locale: this.settings.locale,
34605
+ depth: 0,
34606
+ ...opts,
34607
+ };
34608
+ const feeds = await Promise.all((urls || []).map(async (url) => {
34609
+ try {
34610
+ // Get JSON
34611
+ const json = await this.network.get(url, { nocache: opts?.cache });
34612
+ console.debug(`${this._logPrefix}Loaded JSON from ${url}`, json);
34613
+ // Resolve feed
34614
+ return await this.resolveFeed(json, url, opts);
34615
+ }
34616
+ catch (err) {
34617
+ if (err?.status === 404) {
34618
+ console.error(`${this._logPrefix} Error while fetching feeds at ${url}. 404 (Not found)`);
34619
+ }
34620
+ else {
34621
+ console.error(`${this._logPrefix} Error while fetching feeds at ${url}`, err);
34622
+ }
34623
+ return Promise.resolve([]);
34624
+ }
34625
+ }));
34626
+ const data = feeds.filter(isNotEmptyArray).flat();
34627
+ // Close opened resources
34628
+ if (opts.depth === 0) {
34629
+ this.onAfterLoadAll();
34630
+ }
34631
+ return { data, total: data.length };
34632
+ }
34633
+ async openEditModal(event, feedItem, opts) {
34634
+ const messageId = +feedItem.id;
34635
+ if (isNilOrNaN(messageId))
34636
+ throw new Error(`Cannot load message - invalid id: ${feedItem.id}`);
34637
+ // Fetch the original message
34638
+ const message = await this.messageService.load(messageId);
34639
+ // Count recipients
34640
+ let recipientFilterCount;
34641
+ if (message?.recipientFilter) {
34642
+ recipientFilterCount = await this.personService.countAll(message?.recipientFilter);
34643
+ }
34644
+ const hasTopModal = !!(await this.modalCtrl.getTop());
34645
+ const modal = await this.modalCtrl.create({
34646
+ component: MessageModal,
34647
+ componentProps: {
34648
+ title: 'SOCIAL.FEED.EDIT_TITLE',
34649
+ suggestFn: (value, filter, sortBy, sortDirection, opts) => this.personService.suggest(value, filter, sortBy, sortDirection, opts),
34650
+ canSelectType: false,
34651
+ canRecipientFilter: false,
34652
+ recipientFilterCount,
34653
+ data: message,
34654
+ debug: true,
34655
+ },
34656
+ cssClass: hasTopModal && 'stack-modal',
34657
+ });
34658
+ // Open the modal
34659
+ await modal.present();
34660
+ // On dismiss
34661
+ const { data } = await modal.onDidDismiss();
34662
+ if (!data || !(data instanceof Message))
34663
+ return true; // CANCELLED
34664
+ return await this.messageService.send(data, opts);
34665
+ }
34666
+ async deleteItem(feedItem) {
34667
+ const messageId = +feedItem.id;
34668
+ if (isNilOrNaN(messageId))
34669
+ throw new Error(`Cannot delete message - invalid id: ${feedItem.id}`);
34670
+ return await this.messageService.delete(messageId);
34671
+ }
34672
+ /* -- protected functions -- */
34673
+ async resolveFeed(json, url, opts) {
34674
+ // Check json is JsonFeed compatible
34675
+ if (JsonFeedUtils.isJsonFeed(json)) {
34676
+ // Migrate from old versions
34677
+ let feed = JsonFeedUtils.migrateFeed(json);
34678
+ // Resolve items (e.g. using feed.next_url)
34679
+ feed = await this.resolveFeedItems(feed, { ...opts, depth: opts?.depth ?? 0 });
34680
+ // Truncate html
34681
+ JsonFeedUtils.truncateFeedItemsHtml(feed, opts);
34682
+ return isNotEmptyArray(feed?.items) ? [feed] : [];
34683
+ }
34684
+ // not resolved (unknown format)
34685
+ return [];
34686
+ }
34687
+ async resolveFeedItems(json, opts) {
34688
+ if (!json)
34689
+ return json;
34690
+ // Load items
34691
+ if (isEmptyArray(json.items) && isNotNilOrBlank(json.next_url)) {
34692
+ const depth = opts?.depth ?? 0;
34693
+ if (depth >= 3) {
34694
+ console.warn(`${this._logPrefix}Max depth reached (${depth}) for ${json.feed_url}`);
34695
+ return json;
34696
+ }
34697
+ // Load items
34698
+ const feed = await this.load(json.next_url, { ...opts, depth: depth + 1 });
34699
+ if (isEmptyArray(feed?.items)) {
34700
+ return json;
34701
+ }
34702
+ // Merge with the input json
34703
+ return {
34704
+ ...json,
34705
+ title: json.title ?? feed.title,
34706
+ home_page_url: json.home_page_url ?? feed.home_page_url,
34707
+ authors: isNotEmptyArray(json.authors) ? json.authors : feed.authors,
34708
+ items: feed.items,
34709
+ tag_template: json.tag_template ?? feed.tag_template,
34710
+ };
34711
+ }
34712
+ return json;
34713
+ }
34714
+ onAfterLoadAll() {
34715
+ console.debug(`${this._logPrefix}Feeds loaded`);
34716
+ }
34717
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedService, deps: [{ token: LocalSettingsService }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
34718
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedService });
34719
+ }
34720
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedService, decorators: [{
34721
+ type: Injectable
34722
+ }], ctorParameters: () => [{ type: LocalSettingsService }, { type: Environment, decorators: [{
34723
+ type: Optional
34724
+ }, {
34725
+ type: Inject,
34726
+ args: [ENVIRONMENT]
34727
+ }] }] });
34728
+
34729
+ class FeedDirective {
34730
+ element;
34731
+ _subscription = new Subscription();
34732
+ platform = inject(PlatformService);
34733
+ locationStrategy = inject(LocationStrategy);
34734
+ navController = inject(NavController);
34735
+ router = inject(Router);
34736
+ route = inject(ActivatedRoute);
34737
+ _feedUrl;
34738
+ _baseUrl;
34739
+ set feedUrl(value) {
34740
+ this._feedUrl = value;
34741
+ this._baseUrl = value && JsonFeedUtils.removeJsonExtension(value);
34742
+ }
34743
+ get feedUrl() {
34744
+ return this._feedUrl;
34745
+ }
34746
+ constructor(element) {
34747
+ this.element = element;
34748
+ // DEBUG
34749
+ //console.debug('[feed-directive] Creating feed directive');
34750
+ }
34751
+ ngAfterViewInit() {
34752
+ console.debug('[feed-directive] Processing anchors');
34753
+ // Listening click events
34754
+ this._subscription.add(this.listenClickEvents(this.element.nativeElement));
34755
+ }
34756
+ ngOnDestroy() {
34757
+ this._subscription.unsubscribe();
34758
+ }
34759
+ listenClickEvents(nativeElement) {
34760
+ console.debug('[feed-directive] Start listening click events...');
34761
+ const subscription = new Subscription();
34762
+ const listener = (event) => this.click(event, nativeElement);
34763
+ const links = nativeElement.querySelectorAll('a');
34764
+ links.forEach((link) => {
34765
+ // DEBUG
34766
+ //console.debug('[feed-directive] Adding click listener to', link);
34767
+ link.addEventListener('click', listener);
34768
+ subscription.add(() => link.removeEventListener('click', listener));
34769
+ });
34770
+ // DEBUG
34771
+ subscription.add(() => console.debug('[feed-directive] Stop listening click events'));
34772
+ return subscription;
34773
+ }
34774
+ async click(event, containerElement) {
34775
+ console.debug('[feed-directive] Processing click event...', event);
34776
+ if (!(event.target instanceof HTMLAnchorElement) && !(event.target?.['tagName'] === 'A')) {
34777
+ console.warn('[feed-directive] Invalid click event target. Should an anchor <a>', event.target);
34778
+ return;
34779
+ }
34780
+ const element = event.target;
34781
+ let href = element.getAttribute('href');
34782
+ const fragment = UrlUtils.getFragment(href);
34783
+ // Fragment
34784
+ if (href?.startsWith('#') && isNotNilOrBlank(fragment)) {
34785
+ event.preventDefault();
34786
+ this.scrollToAnchor(containerElement, fragment);
34787
+ return true;
34788
+ }
34789
+ const routerLink = element.getAttribute('routerLink');
34790
+ if (isNotNilOrBlank(routerLink)) {
34791
+ event.preventDefault();
34792
+ return this.navController.navigateForward(routerLink);
34793
+ }
34794
+ // Resolve relative URL, if baseUrl has been set
34795
+ if (this._baseUrl && UrlUtils.isRelativeUrl(href)) {
34796
+ // Resolve URL, then open using the platform
34797
+ href = UrlUtils.resolveRelativeUrl(this._baseUrl, href);
34798
+ }
34799
+ // Resolve internal URL as an app route
34800
+ if (UrlUtils.isInternalUrl(href)) {
34801
+ const routePath = this.normalizeUrl(href.startsWith('/') ? href : `/${href}`);
34802
+ const urlTree = this.getUrlTree(routePath);
34803
+ event.preventDefault();
34804
+ this.router.navigated = false;
34805
+ // Opening route
34806
+ return this.navController.navigateForward(urlTree);
34807
+ }
34808
+ // External URL: open using the platform
34809
+ event.preventDefault();
34810
+ await this.platform.open(href);
34811
+ return true;
34812
+ }
34813
+ scrollToAnchor(containerElement, anchorId) {
34814
+ if (!containerElement || !anchorId) {
34815
+ console.warn("Missing 'containerElement' or 'fragment' argument.");
34816
+ return;
34817
+ }
34818
+ console.debug('[feed-directive-anchor] Scrolling to anchor #' + anchorId);
34819
+ // Find the target element with the specified fragment ID
34820
+ const targetElement = containerElement.querySelector(`#${anchorId}`);
34821
+ if (targetElement) {
34822
+ // Scroll smoothly to the target element
34823
+ // eslint-disable-next-line @rx-angular/prefer-no-layout-sensitive-apis
34824
+ targetElement.scrollIntoView({
34825
+ behavior: 'smooth', // Smooth scrolling behavior
34826
+ block: 'start', // Align the target element to the top of the container
34827
+ });
34828
+ }
34829
+ else {
34830
+ console.warn(`No element found with the ID "${anchorId}" inside the container.`);
34831
+ }
34832
+ }
34833
+ /**
34834
+ * Transform a relative URL to its absolute representation according to current router state.
34835
+ * @param url Relative URL path.
34836
+ * @return Absolute URL based on the current route.
34837
+ */
34838
+ normalizeUrl(url) {
34839
+ if (UrlUtils.isExternalUrl(url)) {
34840
+ return url;
34841
+ }
34842
+ if (this._baseUrl && UrlUtils.isRelativeUrl(url)) {
34843
+ return UrlUtils.resolveRelativeUrl(this._baseUrl, url);
34844
+ }
34845
+ const urlTree = this.getUrlTree(url);
34846
+ const serializedUrl = this.router.serializeUrl(urlTree);
34847
+ return this.locationStrategy.prepareExternalUrl(serializedUrl);
34848
+ }
34849
+ getUrlTree(url) {
34850
+ url = UrlUtils.normalizeUrl(url);
34851
+ const urlPath = UrlUtils.stripFragmentAndQuery(url) || UrlUtils.stripFragmentAndQuery(this.router.url);
34852
+ const parsedUrl = this.router.parseUrl(url);
34853
+ const fragment = parsedUrl.fragment;
34854
+ const queryParams = parsedUrl.queryParams;
34855
+ return this.router.createUrlTree([urlPath], { relativeTo: this.route, fragment, queryParams });
34856
+ }
34857
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
34858
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: FeedDirective, selector: "feed,[feed]", inputs: { feedUrl: ["feed", "feedUrl"] }, ngImport: i0 });
34060
34859
  }
34061
34860
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedDirective, decorators: [{
34062
34861
  type: Directive,
@@ -34068,10 +34867,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
34068
34867
  args: [{ alias: 'feed', required: true }]
34069
34868
  }] } });
34070
34869
 
34071
- const NOOP_ITEM_FILTER = (item) => true;
34072
34870
  class FeedsComponent {
34073
34871
  environment;
34074
34872
  feedService;
34873
+ accountService;
34874
+ alertCtrl;
34075
34875
  _state = new RxState();
34076
34876
  translate = inject(TranslateService);
34077
34877
  platform = inject(PlatformService);
@@ -34079,9 +34879,11 @@ class FeedsComponent {
34079
34879
  router = inject(Router);
34080
34880
  version;
34081
34881
  modalItemId;
34882
+ onRefresh = new EventEmitter();
34082
34883
  feeds$ = this._state.select('feeds');
34083
- feedUrls$ = this._state.select('feedUrls');
34884
+ urls$ = this._state.select('urls');
34084
34885
  hasFeeds$ = this._state.select('hasFeeds');
34886
+ userId$ = this._state.select('userId');
34085
34887
  debug = false;
34086
34888
  mobile;
34087
34889
  showHeader = true;
@@ -34092,17 +34894,19 @@ class FeedsComponent {
34092
34894
  class = '';
34093
34895
  itemId;
34094
34896
  filterItem;
34897
+ editItem = new EventEmitter();
34898
+ deleteItem = new EventEmitter();
34095
34899
  set feeds(value) {
34096
34900
  this._state.set('feeds', () => value);
34097
34901
  }
34098
34902
  get feeds() {
34099
34903
  return this._state.get('feeds');
34100
34904
  }
34101
- set feedUrls(value) {
34102
- this._state.set('feedUrls', () => value);
34905
+ set urls(value) {
34906
+ this._state.set('urls', () => value);
34103
34907
  }
34104
- get feedUrls() {
34105
- return this._state.get('feedUrls');
34908
+ get urls() {
34909
+ return this._state.get('urls');
34106
34910
  }
34107
34911
  get hasFeeds() {
34108
34912
  return this._state.get('hasFeeds');
@@ -34119,6 +34923,9 @@ class FeedsComponent {
34119
34923
  get maxContentLength() {
34120
34924
  return this._state.get('maxContentLength');
34121
34925
  }
34926
+ get peerUrl() {
34927
+ return this._state.get('peerUrl');
34928
+ }
34122
34929
  get hostClass() {
34123
34930
  const classes = [this.class];
34124
34931
  if (this.shape) {
@@ -34127,16 +34934,25 @@ class FeedsComponent {
34127
34934
  return classes.filter((cls) => cls).join(' ');
34128
34935
  }
34129
34936
  modal;
34130
- constructor(settings, environment, feedService) {
34937
+ constructor(settings, environment, feedService, accountService, alertCtrl) {
34131
34938
  this.environment = environment;
34132
34939
  this.feedService = feedService;
34940
+ this.accountService = accountService;
34941
+ this.alertCtrl = alertCtrl;
34133
34942
  this.mobile = settings.mobile;
34943
+ this._state.connect('locale', settings.locale$);
34134
34944
  this._state.connect('hasFeeds', this.feeds$.pipe(map(isNotEmptyArray)));
34945
+ this._state.connect('userId', this.accountService.person$.pipe(map((person) => person?.id)));
34946
+ this._state.connect('peerUrl', this.networkService.peer$.pipe(map((peer) => peer?.url)));
34135
34947
  if (this.feedService) {
34136
- this._state.connect('feeds', this._state.select(['feedUrls', 'maxAgeInMonths', 'maxContentLength']).pipe(switchMap(({ feedUrls, maxAgeInMonths, maxContentLength }) => this.feedService.watchAll({
34137
- urls: feedUrls,
34948
+ this._state.connect('feeds', merge(this.onRefresh.pipe(map(() => ({ ...this._state.get(), cache: false }))), this._state
34949
+ .select(['urls', 'maxAgeInMonths', 'maxContentLength', 'locale'])
34950
+ .pipe(map(({ urls, maxAgeInMonths, maxContentLength, locale }) => ({ urls, maxAgeInMonths, maxContentLength, locale, cache: true })))).pipe(switchMap(({ urls, maxAgeInMonths, maxContentLength, locale, cache }) => this.feedService.watchAll({
34951
+ urls: urls,
34138
34952
  maxAgeInMonths,
34139
34953
  maxContentLength,
34954
+ locale,
34955
+ cache,
34140
34956
  })), map(({ data }) => data)
34141
34957
  // DEBUG
34142
34958
  //tap((feeds) => console.debug('[feed-component] feeds', feeds))
@@ -34144,7 +34960,7 @@ class FeedsComponent {
34144
34960
  }
34145
34961
  }
34146
34962
  ngOnInit() {
34147
- this.feedUrls = this.feedUrls ?? null; // Should set the feedUrls, in order to trigger the select in constructor
34963
+ this.urls = this.urls ?? null; // Should set the urls, in order to trigger the select in constructor
34148
34964
  this.maxAgeInMonths = this.maxAgeInMonths ?? this.environment.feed?.maxAgeInMonths ?? -1;
34149
34965
  this.maxContentLength = this.maxContentLength ?? this.environment.feed?.maxContentLength ?? -1;
34150
34966
  const self = this;
@@ -34220,12 +35036,73 @@ class FeedsComponent {
34220
35036
  getFeedHomeUrl(feed) {
34221
35037
  return feed.home_page_url ?? JsonFeedUtils.removeJsonExtension(feed.feed_url) ?? this.feedService.getHomeUrl();
34222
35038
  }
34223
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedsComponent, deps: [{ token: LocalSettingsService }, { token: ENVIRONMENT }, { token: APP_FEED_SERVICE, optional: true }], target: i0.ɵɵFactoryTarget.Component });
34224
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: FeedsComponent, selector: "app-feed", inputs: { debug: "debug", mobile: "mobile", showHeader: "showHeader", showReadMoreButton: "showReadMoreButton", headerColor: "headerColor", cardColor: "cardColor", shape: "shape", class: "class", itemId: "itemId", filterItem: "filterItem", feeds: "feeds", feedUrls: "feedUrls", maxAgeInMonths: "maxAgeInMonths", maxContentLength: "maxContentLength" }, host: { properties: { "class": "this.hostClass" } }, providers: [RxState], viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n feedUrls: {{ feedUrls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [feedUrls]=\"feedUrls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.first{--top-radius: 0}.feed-content ion-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card ion-card-header .tags{display:inline}.feed-content ion-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}\n"], dependencies: [{ kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonChip, selector: "ion-chip", inputs: ["color", "disabled", "mode", "outline"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "feedUrls", "maxAgeInMonths", "maxContentLength"] }, { kind: "directive", type: FeedDirective, selector: "feed,[feed]", inputs: ["feed"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: ArrayFilterPipe, name: "arrayFilter" }, { kind: "pipe", type: MapPipe, name: "map" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35039
+ /**
35040
+ * Check if the current user is the author of the feed item
35041
+ */
35042
+ canEditItem = (item, userId, feed) => {
35043
+ if (!item || isNil(userId))
35044
+ return false; // No item, or user not connected
35045
+ // DEBUG
35046
+ //console.debug('[feed-component] canEditItem...');
35047
+ // Check feed URL point on the current peer (/api/feed/ ...)
35048
+ if (!item.url?.startsWith(this.peerUrl))
35049
+ return false;
35050
+ // Check item authors first
35051
+ let isAuthor = false;
35052
+ if (isNotEmptyArray(item.authors)) {
35053
+ isAuthor = item.authors.some((author) => +author.id === userId);
35054
+ }
35055
+ // Check feed authors
35056
+ if (!isAuthor && isNotEmptyArray(feed?.authors)) {
35057
+ isAuthor = feed.authors.some((author) => +author.id === userId);
35058
+ }
35059
+ return isAuthor || this.accountService.isAdmin();
35060
+ };
35061
+ /**
35062
+ * Handle edit item action
35063
+ */
35064
+ async onEditItem(event, item) {
35065
+ if (event) {
35066
+ event.preventDefault();
35067
+ event.stopPropagation();
35068
+ }
35069
+ if (this.editItem.observed) {
35070
+ this.editItem.emit(item);
35071
+ }
35072
+ const done = await this.feedService?.openEditModal(event, item);
35073
+ if (done) {
35074
+ // force a refresh of the feeds
35075
+ this.onRefresh.emit();
35076
+ }
35077
+ }
35078
+ /**
35079
+ * Handle delete item action
35080
+ */
35081
+ async onDeleteItem(event, item) {
35082
+ if (event) {
35083
+ event.preventDefault();
35084
+ event.stopPropagation();
35085
+ }
35086
+ // Ask for confirmation before deletion
35087
+ const confirmed = await Alerts.askActionConfirmation(this.alertCtrl, this.translate, true, event);
35088
+ if (confirmed !== true) {
35089
+ return; // User cancelled or dismissed
35090
+ }
35091
+ if (this.deleteItem.observed) {
35092
+ this.deleteItem.emit(item);
35093
+ }
35094
+ const done = await this.feedService?.deleteItem(item);
35095
+ if (done) {
35096
+ // force a refresh of the feeds
35097
+ this.onRefresh.emit();
35098
+ }
35099
+ }
35100
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedsComponent, deps: [{ token: LocalSettingsService }, { token: ENVIRONMENT }, { token: APP_FEED_SERVICE, optional: true }, { token: AccountService }, { token: i2$1.AlertController }], target: i0.ɵɵFactoryTarget.Component });
35101
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: FeedsComponent, selector: "app-feed", inputs: { debug: "debug", mobile: "mobile", showHeader: "showHeader", showReadMoreButton: "showReadMoreButton", headerColor: "headerColor", cardColor: "cardColor", shape: "shape", class: "class", itemId: "itemId", filterItem: "filterItem", feeds: "feeds", urls: "urls", maxAgeInMonths: "maxAgeInMonths", maxContentLength: "maxContentLength" }, outputs: { editItem: "editItem", deleteItem: "deleteItem" }, host: { properties: { "class": "this.hostClass" } }, providers: [RxState], viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"], dependencies: [{ kind: "component", type: i2$1.IonAvatar, selector: "ion-avatar" }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonChip, selector: "ion-chip", inputs: ["color", "disabled", "mode", "outline"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonImg, selector: "ion-img", inputs: ["alt", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonNote, selector: "ion-note", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonModal, selector: "ion-modal" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: i4$2.MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: DebugComponent, selector: "app-debug", inputs: ["titlePrefix", "title", "enable", "expanded"] }, { kind: "directive", type: MarkdownDirective, selector: "markdown,[markdown]" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "directive", type: FeedDirective, selector: "feed,[feed]", inputs: ["feed"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: ArrayFirstPipe, name: "arrayFirst" }, { kind: "pipe", type: ArrayFilterPipe, name: "arrayFilter" }, { kind: "pipe", type: MapPipe, name: "map" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34225
35102
  }
34226
35103
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedsComponent, decorators: [{
34227
35104
  type: Component,
34228
- args: [{ selector: 'app-feed', providers: [RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n feedUrls: {{ feedUrls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [feedUrls]=\"feedUrls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.first{--top-radius: 0}.feed-content ion-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card ion-card-header .tags{display:inline}.feed-content ion-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}\n"] }]
35105
+ args: [{ selector: 'app-feed', providers: [RxState], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- debug -->\n@if (debug) {\n <app-debug [title]=\"'Feed'\">\n <p>\n hasFeeds?: {{ hasFeeds$ | async }}\n <br />\n urls: {{ urls$ | async | json }}\n <br />\n shape: {{ shape }}\n </p>\n </app-debug>\n}\n\n@let feeds = feeds$ | async;\n@let userId = userId$ | async;\n\n@if (feeds | isNotEmptyArray) {\n <!-- top header -->\n @if (showHeader && (feeds | arrayFirst); as feed) {\n <ion-item lines=\"none\" [color]=\"headerColor\" class=\"feed-header shape-{{ shape }}\">\n <ion-icon slot=\"start\" name=\"megaphone\"></ion-icon>\n <ion-label>\n <b>{{ feed.title || ('SOCIAL.FEED.NEWS' | translate) }}</b>\n </ion-label>\n <ion-button slot=\"end\" fill=\"clear\" (click)=\"openFeedHome()\" shape=\"\">\n <ion-label translate>SOCIAL.FEED.SHOW_ALL_FEED</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n </ion-item>\n }\n\n <div class=\"feed-content shape-{{ shape }} ion-no-padding\" [class.has-header]=\"showHeader\">\n <!-- feeds -->\n @for (feed of feeds; track feed.feed_url; let firstFeed = $first; let lastFeed = $last) {\n <!-- items -->\n @for (item of feed.items | arrayFilter: filterItem; track item.id; let firstItem = $first; let lastItem = $last) {\n <ion-card\n [class.first]=\"firstFeed && firstItem\"\n [class.last]=\"lastFeed && lastItem\"\n [color]=\"cardColor !== 'light-transparent' ? cardColor : undefined\"\n class=\"feed-item-card\"\n >\n <ion-card-header>\n <ion-card-subtitle style=\"vertical-align: middle\">\n <!-- Authors -->\n @for (author of item.authors || feed.authors; track author) {\n @if (author.name || author.avatar) {\n <ion-chip (click)=\"openUrl(author.url)\" tappable>\n @if (author.avatar) {\n <ion-avatar>\n <ion-img [src]=\"author.avatar\" [alt]=\"author.name\"></ion-img>\n </ion-avatar>\n }\n @if (author.name) {\n <ion-label class=\"author\">{{ author.name }}</ion-label>\n }\n </ion-chip>\n }\n }\n <ion-note class=\"ion-float-end\">\n <small>{{ item.date_published | dateFromNow }}</small>\n </ion-note>\n </ion-card-subtitle>\n\n <!-- title -->\n <ion-card-title (click)=\"openFeedItem(item, feed)\" tappable>{{ item?.title }}</ion-card-title>\n\n <!-- tags -->\n @let tags = item | map: getTags;\n @if (tags | isNotEmptyArray) {\n <ion-text class=\"tags\">\n @for (tag of tags; track tag; let last = $last) {\n <a (click)=\"openTag(feed, tag)\" tappable>\n <ion-text>#{{ tag }}</ion-text>\n </a>\n @if (!last) {\n &nbsp;\n }\n }\n </ion-text>\n }\n </ion-card-header>\n\n <!-- Feed content -->\n <ion-card-content>\n <ion-text [feed]=\"item.url || feed.feed_url\">\n @if (item.content_html) {\n <p [innerHTML]=\"item.content_html\"></p>\n } @else if (item.content_text) {\n <p>\n <markdown [data]=\"item.content_text\" emoji></markdown>\n </p>\n }\n </ion-text>\n </ion-card-content>\n\n @let editable = canEditItem(item, userId, feed);\n @if (editable || showReadMoreButton) {\n @if (editable) {\n\n <!-- Delete button (visible hover)-->\n <button\n mat-icon-button\n (click)=\"onDeleteItem($event, item)\"\n class=\"visible-hover ion-float-start\"\n [title]=\"'COMMON.BTN_DELETE' | translate\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n <!-- Edit button (visible hover) -->\n <ion-button\n (click)=\"onEditItem($event, item)\"\n class=\"visible-hover ion-float-start\" fill=\"clear\"\n >\n <mat-icon slot=\"start\">edit</mat-icon>\n<!-- <ion-icon name=\"pencil\" slot=\"start\"></ion-icon>-->\n <ion-label translate>COMMON.BTN_EDIT</ion-label>\n </ion-button>\n }\n @if (showReadMoreButton) {\n <ion-button (click)=\"openFeedItem(item, feed)\" class=\"ion-float-end\" fill=\"clear\">\n <ion-label>{{ (item.truncated ? 'SOCIAL.FEED.READ_MORE' : 'COMMON.BTN_SHOW') | translate }}</ion-label>\n <ion-icon slot=\"end\" name=\"chevron-forward-outline\"></ion-icon>\n </ion-button>\n }\n }\n </ion-card>\n }\n }\n </div>\n}\n\n<!-- Details modal -->\n<ion-modal #modal [showBackdrop]=\"false\"\n class=\"stack-modal\" [class.modal-large]=\"!mobile\">\n <ng-template>\n <ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-title>{{ 'SOCIAL.FEED.NEWS' | translate }}</ion-title>\n <ion-buttons slot=\"end\">\n <ion-button (click)=\"modal.dismiss()\">\n <ion-icon slot=\"icon-only\" name=\"close\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </ion-header>\n <ion-content>\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"urls\"\n [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [maxContentLength]=\"-1\"\n [maxAgeInMonths]=\"-1\"\n [itemId]=\"modalItemId\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n </ion-content>\n </ng-template>\n</ion-modal>\n", styles: [":host{display:block;height:calc(100% - 10px);max-height:fit-content;overflow:hidden;--feed-header-height: 48px;-webkit-box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px rgba(0,0,0,.14),0 1px 5px rgba(0,0,0,.12);-webkit-margin-start:10px;margin-inline-start:10px;-webkit-margin-end:10px;margin-inline-end:10px;margin-top:0;margin-bottom:10px;--feed-border-radius: 4px;border-radius:var(--feed-border-radius);--ion-card-background: rgba(var(--ion-background-color-rgb), .6)}:host.shape-round{--feed-border-radius: 12px}ion-button{text-transform:unset;--color: rgba(var(--ion-color-contrast-rgb), .7)}ion-item.feed-header{height:var(--feed-header-height);margin:0;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:var(--feed-border-radius) var(--feed-border-radius) 0 0}ion-item.feed-header ion-icon[slot=start]{-webkit-margin-end:8px;margin-inline-end:8px}.feed-content{height:auto;overflow-y:auto;--margin-bottom: 8px}.feed-content.has-header{height:calc(100% - var(--feed-header-height, 0))}.feed-content ion-card.feed-item-card{--top-radius: 4px;--bottom-radius: 4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:0 0 var(--margin-bottom) 0;border-radius:var(--top-radius) var(--top-radius) var(--bottom-radius) var(--bottom-radius);--ion-card-color-contrast-rgb: var(--ion-color-contrast-rgb, var(--ion-color-dark-rgb, 0, 0, 0));--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card.first{--top-radius: 0}.feed-content ion-card.feed-item-card.last{--margin-bottom: 0;--bottom-radius: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-title{--color: rgba(var(--ion-card-color-contrast-rgb), .87)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip{--background: transparent;--border-color: transparent;--border-width: 0}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-avatar{--color: rgba(var(--ion-card-color-contrast-rgb), .6);border:1px solid var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-chip ion-label{--color: rgba(var(--ion-card-color-contrast-rgb), .8);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header ion-card-subtitle ion-note{--color: rgba(var(--ion-card-color-contrast-rgb), .6);color:var(--color)}.feed-content ion-card.feed-item-card ion-card-header .tags{display:inline}.feed-content ion-card.feed-item-card ion-card-header .tags a{color:rgba(var(--ion-card-color-contrast-rgb),.6)!important}.feed-content ion-card.feed-item-card ion-card-content ion-text ::ng-deep img{max-width:100%;height:auto!important}.feed-content ion-card.feed-item-card .visible-hover{opacity:0;transition:opacity .2s ease-in-out}.feed-content ion-card.feed-item-card:hover .visible-hover{opacity:1}\n"] }]
34229
35106
  }], ctorParameters: () => [{ type: LocalSettingsService }, { type: Environment, decorators: [{
34230
35107
  type: Inject,
34231
35108
  args: [ENVIRONMENT]
@@ -34234,7 +35111,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
34234
35111
  }, {
34235
35112
  type: Inject,
34236
35113
  args: [APP_FEED_SERVICE]
34237
- }] }], propDecorators: { debug: [{
35114
+ }] }, { type: AccountService }, { type: i2$1.AlertController }], propDecorators: { debug: [{
34238
35115
  type: Input
34239
35116
  }], mobile: [{
34240
35117
  type: Input
@@ -34254,9 +35131,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
34254
35131
  type: Input
34255
35132
  }], filterItem: [{
34256
35133
  type: Input
35134
+ }], editItem: [{
35135
+ type: Output
35136
+ }], deleteItem: [{
35137
+ type: Output
34257
35138
  }], feeds: [{
34258
35139
  type: Input
34259
- }], feedUrls: [{
35140
+ }], urls: [{
34260
35141
  type: Input
34261
35142
  }], maxAgeInMonths: [{
34262
35143
  type: Input
@@ -34643,11 +35524,11 @@ class HomePage extends RxState {
34643
35524
  this.cd.markForCheck();
34644
35525
  }
34645
35526
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HomePage, deps: [{ token: AccountService }, { token: i2$1.ModalController }, { token: i1$1.TranslateService }, { token: i2$1.ToastController }, { token: ConfigService }, { token: PlatformService }, { token: i0.ChangeDetectorRef }, { token: NetworkService }, { token: LocalSettingsService }, { token: ENVIRONMENT }, { token: APP_LOCALES }, { token: APP_HOME_BUTTONS, optional: true }, { token: APP_HOME_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Component });
34646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: HomePage, selector: "app-page-home", viewQueries: [{ propertyName: "content", first: true, predicate: IonContent, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar [canGoBack]=\"false\" visible-xs visible-sm visible-mobile>\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon [autoHide]=\"autoHideNotificationIcon\"></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button (click)=\"toggleDarkMode()\">\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'sunny' : 'moon'\"></ion-icon>\n </ion-button>\n }\n\n <!-- Change locale button -->\n <ion-button [matMenuTriggerFor]=\"localeMenu\">\n <ion-icon slot=\"start\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <ng-template matMenuContent>\n @for (item of locales; track item.key) {\n <button mat-menu-item (click)=\"changeLanguage(item.key)\">\n <!--<mat-icon>{{ currentLocale === item.key ? 'checkmark' : undefined }}</mat-icon>-->\n <ion-label>{{ item.value }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" [class.has-scrollbar]=\"hasScrollbar\" no-padding-xs>\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!(loading$ | push)\">\n @if (showSpinner) {\n <div class=\"spinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS' | translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n }\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n @if (!mobile) {\n <div class=\"hidden-xs hidden-sm\">\n <ion-toolbar color=\"traansparent\" translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon\n [style]=\"'ion-button'\"\n color=\"secondary\"\n unreadColor=\"tertiary\"\n fill=\"solid\"\n [autoHide]=\"autoHideNotificationIcon\"\n ></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n (click)=\"toggleDarkMode()\"\n [title]=\"'HOME.BTN_DARK_MODE' | translate\"\n >\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'moon' : 'sunny'\"></ion-icon>\n </ion-button>\n }\n <!-- change locale button -->\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n [matMenuTriggerFor]=\"localeMenu\"\n [title]=\"'SETTINGS.LOCALE' | translate\"\n >\n <ion-icon slot=\"icon-only\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n }\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n @if (!(loading$ | push)) {\n @let hasFeed = hasFeed$ | async;\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 3 : 0\"></ion-col>\n <ion-col>\n <!-- Welcome card -->\n <ion-card class=\"main welcome ion-padding ion-text-center ion-align-self-center\" @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB' | translate: { appName: appName }\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP' | translate: { appName: appName }\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\"></ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img *ngIf=\"logo\" [attr.src]=\"logo\" alt=\"Logo\" />\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br />\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons\">\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n <!-- Feature buttons -->\n <ng-container *rxIf=\"$filteredButtons; let buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button\n *ngIf=\"item.path\"\n expand=\"block\"\n color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n >\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">\n {{ item.matIcon }}\n </mat-icon>\n <ion-text>{{ 'HOME.BTN_DATA_ENTRY' | translate: { name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{ item.title }}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text\n [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: { displayName: accountName }\"\n ></ion-text>\n <br />\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n </ng-template>\n </div>\n </ion-card-content>\n </ion-card>\n </ion-col>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 1 : 0\"></ion-col>\n <ion-col [class.feed]=\"hasFeed\" [size]=\"hasFeed ? 12 : 0\" [sizeXl]=\"hasFeed ? 4 : 0\">\n @if (showFeed) {\n @if (mobile) {\n <app-feed\n #feed\n shape=\"round\"\n [feedUrls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @fadeInAnimation\n ></app-feed>\n } @else {\n <app-feed\n #feed\n shape=\"round\"\n [debug]=\"false\"\n [feedUrls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @slideInAnimation\n @fadeInAnimation\n ></app-feed>\n }\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Page history -->\n @let pageHistory = isLogin && (pageHistory$ | push);\n @if (pageHistory | isNotEmptyArray) {\n <ion-grid class=\"history-container ion-align-self-center\">\n <ion-row>\n @for (page of pageHistory; track page.path) {\n <ion-col size=\"12\" size-xl=\"\" class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"removePageHistory(page.path)\"\n mat-icon-button\n class=\"ion-float-start ion-no-margin close-button\"\n >\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle | translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time | dateFormat: { time: true }\">\n <small>\n <ion-icon name=\"time-outline\"></ion-icon>\n &nbsp;{{ page.time | dateFromNow }}\n </small>\n &nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\"\n lines=\"none\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{ page.matIcon }}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n @if (page.children | isNotEmptyArray) {\n <ion-card-content class=\"ion-no-padding ion-padding-start\">\n <ion-list class=\"ion-no-padding\">\n @for (childPage of page.children; track childPage.path) {\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">\n {{ childPage.matIcon }}\n </mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card-content>\n }\n </ion-card>\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n }\n\n <!-- Bottom banner -->\n @if (showPartnerBanner || showLegalInformation) {\n <ion-grid\n class=\"bottom-banner ion-text-center\"\n [class.floating]=\"!mobile && (pageHistory | isEmptyArray)\"\n @fadeInAnimation\n >\n <ion-row>\n <!-- partners logos -->\n @if (showPartnerBanner) {\n <ion-col size=\"12\" [sizeLg]=\"partnerBannerSize\" class=\"ion-text-center\">\n @if (showLegalInformation) {\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.PARTNERS' | translate }}</ion-list-header>\n </ion-list>\n }\n <!-- partners -->\n @for (item of $partners | async; track $index) {\n <a href=\"{{ item.siteUrl }}\">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{ item.label }}\" [title]=\"item.label\" />\n </a>\n }\n </ion-col>\n }\n\n <!-- legal information -->\n @if (showLegalInformation) {\n <ion-col size=\"12\" size-sm=\"6\" size-lg=\"4\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header class=\"ion-text-left\">{{ 'HOME.LEGAL_INFORMATION' | translate }}</ion-list-header>\n @for (item of legalInformation; track item.path) {\n <ion-item (click)=\"openMarkdownModal($event, item)\" lines=\"none\" [button]=\"true\">\n <ion-label>\n <p>\n {{ item.title | translate: item.titleArgs }}\n </p>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-col>\n @if (showInstallLinks) {\n <ion-col size=\"12\" size-lg=\"3\" offset-lg=\"9\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.INSTALL_APP' | translate }}</ion-list-header>\n <!-- TODO -->\n </ion-list>\n </ion-col>\n }\n }\n </ion-row>\n </ion-grid>\n }\n }\n\n <!-- image credits -->\n @if (contentCredits | isNotNilOrBlank) {\n <div class=\"content-credits\" [class.floating]=\"!mobile\">\n <ion-text>{{ contentCredits }}</ion-text>\n </div>\n }\n</ion-content>\n", styles: [":host{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .7);--scrollbar-width: 0;--bottom-banner-height: 122px}h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-color:var(--ion-background-color);display:inline-block;padding:15px}ion-content.has-scrollbar{--scrollbar-width: 8px}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:transparent;z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:var(--ion-card-background-color)}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:var(--ion-card-background-color);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{--mdc-icon-button-state-layer-size: 40px;padding:8px;z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{width:100%;--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .5);background-color:var(--ion-card-background-color);min-width:240px;z-index:0;border-radius:4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content .bottom-banner ion-list-header{min-height:30px}ion-content .bottom-banner ion-item{--min-height: 28px}ion-content .bottom-banner ion-item ion-label{margin-top:3px!important;margin-bottom:3px!important}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner.floating{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - var(--bottom-banner-height))}}@media screen and (min-width: 1200px){ion-content ion-col.feed{height:fit-content;max-height:calc(100vh - var(--ion-toolbar-height) - var(--bottom-banner-height) - var(--ion-padding) * 2);padding-inline-end:calc(var(--ion-padding) - 10px);overflow:hidden}ion-content .bottom-banner.floating{position:fixed;left:0;right:0;bottom:0;overflow-y:auto;max-height:var(--bottom-banner-height);margin-top:0}}.content-credits{float:right}.content-credits.floating{float:unset;position:fixed;bottom:0;right:var(--scrollbar-width, 0)}.content-credits ion-text{padding-inline-start:4px;padding-inline-end:4px;font-size:.7rem;background-color:rgba(var(--ion-background-color-rgb),.5);color:rgba(var(--ion-text-color-rgb),.7)}@media screen and (min-width: 992px){.left-border{border-left:1px solid var(--ion-color-medium)}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2$1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i8$2.RxIf, selector: "[rxIf]", inputs: ["rxIf", "rxIfStrategy", "rxIfElse", "rxIfThen", "rxIfSuspense", "rxIfComplete", "rxIfError", "rxIfContextTrigger", "rxIfNextTrigger", "rxIfSuspenseTrigger", "rxIfErrorTrigger", "rxIfCompleteTrigger", "rxIfParent", "rxIfPatchZone", "rxIfRenderCallback"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i8$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$1.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i8$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: NgInitDirective, selector: "[ngInit]", outputs: ["ngInit"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearch"] }, { kind: "directive", type: i1$6.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton", "debug"] }, { kind: "component", type: UserEventNotificationIcon, selector: "app-user-event-notification-icon", inputs: ["titleI18n", "disabled", "filter", "autoHide", "headerActions", "footerActions", "style", "color", "unreadColor", "fill", "mobile", "listStyle", "debug"], outputs: ["showList"] }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "feedUrls", "maxAgeInMonths", "maxContentLength"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i19.RxPush, name: "push" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }], animations: [fadeInAnimation, slideUpDownAnimation, slideInAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35527
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: HomePage, selector: "app-page-home", viewQueries: [{ propertyName: "content", first: true, predicate: IonContent, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<app-toolbar [canGoBack]=\"false\" visible-xs visible-sm visible-mobile>\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon [autoHide]=\"autoHideNotificationIcon\"></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button (click)=\"toggleDarkMode()\">\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'sunny' : 'moon'\"></ion-icon>\n </ion-button>\n }\n\n <!-- Change locale button -->\n <ion-button [matMenuTriggerFor]=\"localeMenu\">\n <ion-icon slot=\"start\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <ng-template matMenuContent>\n @for (item of locales; track item.key) {\n <button mat-menu-item (click)=\"changeLanguage(item.key)\">\n <!--<mat-icon>{{ currentLocale === item.key ? 'checkmark' : undefined }}</mat-icon>-->\n <ion-label>{{ item.value }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" [class.has-scrollbar]=\"hasScrollbar\" no-padding-xs>\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!(loading$ | push)\">\n @if (showSpinner) {\n <div class=\"spinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS' | translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n }\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n @if (!mobile) {\n <div class=\"hidden-xs hidden-sm\">\n <ion-toolbar color=\"traansparent\" translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon\n [style]=\"'ion-button'\"\n color=\"secondary\"\n unreadColor=\"tertiary\"\n fill=\"solid\"\n [autoHide]=\"autoHideNotificationIcon\"\n ></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n (click)=\"toggleDarkMode()\"\n [title]=\"'HOME.BTN_DARK_MODE' | translate\"\n >\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'moon' : 'sunny'\"></ion-icon>\n </ion-button>\n }\n <!-- change locale button -->\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n [matMenuTriggerFor]=\"localeMenu\"\n [title]=\"'SETTINGS.LOCALE' | translate\"\n >\n <ion-icon slot=\"icon-only\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n }\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n @if (!(loading$ | push)) {\n @let hasFeed = hasFeed$ | async;\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 3 : 0\"></ion-col>\n <ion-col>\n <!-- Welcome card -->\n <ion-card class=\"main welcome ion-padding ion-text-center ion-align-self-center\" @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB' | translate: { appName: appName }\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP' | translate: { appName: appName }\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\"></ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img *ngIf=\"logo\" [attr.src]=\"logo\" alt=\"Logo\" />\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br />\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons\">\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n <!-- Feature buttons -->\n <ng-container *rxIf=\"$filteredButtons; let buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button\n *ngIf=\"item.path\"\n expand=\"block\"\n color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n >\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">\n {{ item.matIcon }}\n </mat-icon>\n <ion-text>{{ 'HOME.BTN_DATA_ENTRY' | translate: { name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{ item.title }}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text\n [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: { displayName: accountName }\"\n ></ion-text>\n <br />\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n </ng-template>\n </div>\n </ion-card-content>\n </ion-card>\n </ion-col>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 1 : 0\"></ion-col>\n <ion-col [class.feed]=\"hasFeed\" [size]=\"hasFeed ? 12 : 0\" [sizeXl]=\"hasFeed ? 4 : 0\">\n @if (showFeed) {\n @if (mobile) {\n <app-feed\n #feed\n shape=\"round\"\n [urls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @fadeInAnimation\n ></app-feed>\n } @else {\n <app-feed\n #feed\n shape=\"round\"\n [debug]=\"false\"\n [urls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @slideInAnimation\n @fadeInAnimation\n ></app-feed>\n }\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Page history -->\n @let pageHistory = isLogin && (pageHistory$ | push);\n @if (pageHistory | isNotEmptyArray) {\n <ion-grid class=\"history-container ion-align-self-center\">\n <ion-row>\n @for (page of pageHistory; track page.path) {\n <ion-col size=\"12\" size-xl=\"\" class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"removePageHistory(page.path)\"\n mat-icon-button\n class=\"ion-float-start ion-no-margin close-button\"\n >\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle | translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time | dateFormat: { time: true }\">\n <small>\n <ion-icon name=\"time-outline\"></ion-icon>\n &nbsp;{{ page.time | dateFromNow }}\n </small>\n &nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\"\n lines=\"none\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{ page.matIcon }}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n @if (page.children | isNotEmptyArray) {\n <ion-card-content class=\"ion-no-padding ion-padding-start\">\n <ion-list class=\"ion-no-padding\">\n @for (childPage of page.children; track childPage.path) {\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">\n {{ childPage.matIcon }}\n </mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card-content>\n }\n </ion-card>\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n }\n\n <!-- Bottom banner -->\n @if (showPartnerBanner || showLegalInformation) {\n <ion-grid\n class=\"bottom-banner ion-text-center\"\n [class.floating]=\"!mobile && (pageHistory | isEmptyArray)\"\n @fadeInAnimation\n >\n <ion-row>\n <!-- partners logos -->\n @if (showPartnerBanner) {\n <ion-col size=\"12\" [sizeLg]=\"partnerBannerSize\" class=\"ion-text-center\">\n @if (showLegalInformation) {\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.PARTNERS' | translate }}</ion-list-header>\n </ion-list>\n }\n <!-- partners -->\n @for (item of $partners | async; track $index) {\n <a href=\"{{ item.siteUrl }}\">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{ item.label }}\" [title]=\"item.label\" />\n </a>\n }\n </ion-col>\n }\n\n <!-- legal information -->\n @if (showLegalInformation) {\n <ion-col size=\"12\" size-sm=\"6\" size-lg=\"4\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header class=\"ion-text-left\">{{ 'HOME.LEGAL_INFORMATION' | translate }}</ion-list-header>\n @for (item of legalInformation; track item.path) {\n <ion-item (click)=\"openMarkdownModal($event, item)\" lines=\"none\" [button]=\"true\">\n <ion-label>\n <p>\n {{ item.title | translate: item.titleArgs }}\n </p>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-col>\n @if (showInstallLinks) {\n <ion-col size=\"12\" size-lg=\"3\" offset-lg=\"9\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.INSTALL_APP' | translate }}</ion-list-header>\n <!-- TODO -->\n </ion-list>\n </ion-col>\n }\n }\n </ion-row>\n </ion-grid>\n }\n }\n\n <!-- image credits -->\n @if (contentCredits | isNotNilOrBlank) {\n <div class=\"content-credits\" [class.floating]=\"!mobile\">\n <ion-text>{{ contentCredits }}</ion-text>\n </div>\n }\n</ion-content>\n", styles: [":host{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .7);--scrollbar-width: 0;--bottom-banner-height: 122px}h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-color:var(--ion-background-color);display:inline-block;padding:15px}ion-content.has-scrollbar{--scrollbar-width: 8px}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:transparent;z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:var(--ion-card-background-color)}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:var(--ion-card-background-color);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{--mdc-icon-button-state-layer-size: 40px;padding:8px;z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{width:100%;--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .5);background-color:var(--ion-card-background-color);min-width:240px;z-index:0;border-radius:4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content .bottom-banner ion-list-header{min-height:30px}ion-content .bottom-banner ion-item{--min-height: 28px}ion-content .bottom-banner ion-item ion-label{margin-top:3px!important;margin-bottom:3px!important}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner.floating{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - var(--bottom-banner-height))}}@media screen and (min-width: 1200px){ion-content ion-col.feed{height:fit-content;max-height:calc(100vh - var(--ion-toolbar-height) - var(--bottom-banner-height) - var(--ion-padding) * 2);padding-inline-end:calc(var(--ion-padding) - 10px);overflow:hidden}ion-content .bottom-banner.floating{position:fixed;left:0;right:0;bottom:0;overflow-y:auto;max-height:var(--bottom-banner-height);margin-top:0}}.content-credits{float:right}.content-credits.floating{float:unset;position:fixed;bottom:0;right:var(--scrollbar-width, 0)}.content-credits ion-text{padding-inline-start:4px;padding-inline-end:4px;font-size:.7rem;background-color:rgba(var(--ion-background-color-rgb),.5);color:rgba(var(--ion-text-color-rgb),.7)}@media screen and (min-width: 992px){.left-border{border-left:1px solid var(--ion-color-medium)}}\n"], dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i2$1.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i2$1.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i2$1.IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i2$1.IonListHeader, selector: "ion-list-header", inputs: ["color", "lines", "mode"] }, { kind: "component", type: i2$1.IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonText, selector: "ion-text", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i2$1.RouterLinkDelegate, selector: ":not(a):not(area)[routerLink]" }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i8$2.RxIf, selector: "[rxIf]", inputs: ["rxIf", "rxIfStrategy", "rxIfElse", "rxIfThen", "rxIfSuspense", "rxIfComplete", "rxIfError", "rxIfContextTrigger", "rxIfNextTrigger", "rxIfSuspenseTrigger", "rxIfErrorTrigger", "rxIfCompleteTrigger", "rxIfParent", "rxIfPatchZone", "rxIfRenderCallback"] }, { kind: "component", type: i6$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: i9.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i8$1.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i8$1.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i8$1.MatMenuContent, selector: "ng-template[matMenuContent]" }, { kind: "directive", type: i8$1.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "directive", type: NgInitDirective, selector: "[ngInit]", outputs: ["ngInit"] }, { kind: "component", type: ToolbarComponent, selector: "app-toolbar", inputs: ["progressBarMode", "title", "color", "class", "id", "backHref", "defaultBackHref", "hasValidate", "hasClose", "hasSearch", "canGoBack", "canShowMenu"], outputs: ["onValidate", "onClose", "onValidateAndClose", "onBackClick", "onSearch"] }, { kind: "directive", type: i1$6.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: AppInstallUpgradeCard, selector: "app-install-upgrade-card", inputs: ["isLogin", "showUpgradeWarning", "showOfflineWarning", "showInstallButton", "debug"] }, { kind: "component", type: UserEventNotificationIcon, selector: "app-user-event-notification-icon", inputs: ["titleI18n", "disabled", "filter", "autoHide", "headerActions", "footerActions", "style", "color", "unreadColor", "fill", "mobile", "listStyle", "debug"], outputs: ["showList"] }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: i19.RxPush, name: "push" }, { kind: "pipe", type: DateFormatPipe, name: "dateFormat" }, { kind: "pipe", type: DateFromNowPipe, name: "dateFromNow" }, { kind: "pipe", type: NotEmptyArrayPipe, name: "isNotEmptyArray" }, { kind: "pipe", type: EmptyArrayPipe, name: "isEmptyArray" }, { kind: "pipe", type: IsNotNilOrBlankPipe, name: "isNotNilOrBlank" }], animations: [fadeInAnimation, slideUpDownAnimation, slideInAnimation], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34647
35528
  }
34648
35529
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HomePage, decorators: [{
34649
35530
  type: Component,
34650
- args: [{ selector: 'app-page-home', changeDetection: ChangeDetectionStrategy.OnPush, animations: [fadeInAnimation, slideUpDownAnimation, slideInAnimation], template: "<app-toolbar [canGoBack]=\"false\" visible-xs visible-sm visible-mobile>\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon [autoHide]=\"autoHideNotificationIcon\"></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button (click)=\"toggleDarkMode()\">\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'sunny' : 'moon'\"></ion-icon>\n </ion-button>\n }\n\n <!-- Change locale button -->\n <ion-button [matMenuTriggerFor]=\"localeMenu\">\n <ion-icon slot=\"start\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <ng-template matMenuContent>\n @for (item of locales; track item.key) {\n <button mat-menu-item (click)=\"changeLanguage(item.key)\">\n <!--<mat-icon>{{ currentLocale === item.key ? 'checkmark' : undefined }}</mat-icon>-->\n <ion-label>{{ item.value }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" [class.has-scrollbar]=\"hasScrollbar\" no-padding-xs>\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!(loading$ | push)\">\n @if (showSpinner) {\n <div class=\"spinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS' | translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n }\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n @if (!mobile) {\n <div class=\"hidden-xs hidden-sm\">\n <ion-toolbar color=\"traansparent\" translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon\n [style]=\"'ion-button'\"\n color=\"secondary\"\n unreadColor=\"tertiary\"\n fill=\"solid\"\n [autoHide]=\"autoHideNotificationIcon\"\n ></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n (click)=\"toggleDarkMode()\"\n [title]=\"'HOME.BTN_DARK_MODE' | translate\"\n >\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'moon' : 'sunny'\"></ion-icon>\n </ion-button>\n }\n <!-- change locale button -->\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n [matMenuTriggerFor]=\"localeMenu\"\n [title]=\"'SETTINGS.LOCALE' | translate\"\n >\n <ion-icon slot=\"icon-only\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n }\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n @if (!(loading$ | push)) {\n @let hasFeed = hasFeed$ | async;\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 3 : 0\"></ion-col>\n <ion-col>\n <!-- Welcome card -->\n <ion-card class=\"main welcome ion-padding ion-text-center ion-align-self-center\" @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB' | translate: { appName: appName }\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP' | translate: { appName: appName }\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\"></ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img *ngIf=\"logo\" [attr.src]=\"logo\" alt=\"Logo\" />\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br />\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons\">\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n <!-- Feature buttons -->\n <ng-container *rxIf=\"$filteredButtons; let buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button\n *ngIf=\"item.path\"\n expand=\"block\"\n color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n >\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">\n {{ item.matIcon }}\n </mat-icon>\n <ion-text>{{ 'HOME.BTN_DATA_ENTRY' | translate: { name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{ item.title }}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text\n [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: { displayName: accountName }\"\n ></ion-text>\n <br />\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n </ng-template>\n </div>\n </ion-card-content>\n </ion-card>\n </ion-col>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 1 : 0\"></ion-col>\n <ion-col [class.feed]=\"hasFeed\" [size]=\"hasFeed ? 12 : 0\" [sizeXl]=\"hasFeed ? 4 : 0\">\n @if (showFeed) {\n @if (mobile) {\n <app-feed\n #feed\n shape=\"round\"\n [feedUrls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @fadeInAnimation\n ></app-feed>\n } @else {\n <app-feed\n #feed\n shape=\"round\"\n [debug]=\"false\"\n [feedUrls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @slideInAnimation\n @fadeInAnimation\n ></app-feed>\n }\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Page history -->\n @let pageHistory = isLogin && (pageHistory$ | push);\n @if (pageHistory | isNotEmptyArray) {\n <ion-grid class=\"history-container ion-align-self-center\">\n <ion-row>\n @for (page of pageHistory; track page.path) {\n <ion-col size=\"12\" size-xl=\"\" class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"removePageHistory(page.path)\"\n mat-icon-button\n class=\"ion-float-start ion-no-margin close-button\"\n >\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle | translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time | dateFormat: { time: true }\">\n <small>\n <ion-icon name=\"time-outline\"></ion-icon>\n &nbsp;{{ page.time | dateFromNow }}\n </small>\n &nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\"\n lines=\"none\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{ page.matIcon }}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n @if (page.children | isNotEmptyArray) {\n <ion-card-content class=\"ion-no-padding ion-padding-start\">\n <ion-list class=\"ion-no-padding\">\n @for (childPage of page.children; track childPage.path) {\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">\n {{ childPage.matIcon }}\n </mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card-content>\n }\n </ion-card>\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n }\n\n <!-- Bottom banner -->\n @if (showPartnerBanner || showLegalInformation) {\n <ion-grid\n class=\"bottom-banner ion-text-center\"\n [class.floating]=\"!mobile && (pageHistory | isEmptyArray)\"\n @fadeInAnimation\n >\n <ion-row>\n <!-- partners logos -->\n @if (showPartnerBanner) {\n <ion-col size=\"12\" [sizeLg]=\"partnerBannerSize\" class=\"ion-text-center\">\n @if (showLegalInformation) {\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.PARTNERS' | translate }}</ion-list-header>\n </ion-list>\n }\n <!-- partners -->\n @for (item of $partners | async; track $index) {\n <a href=\"{{ item.siteUrl }}\">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{ item.label }}\" [title]=\"item.label\" />\n </a>\n }\n </ion-col>\n }\n\n <!-- legal information -->\n @if (showLegalInformation) {\n <ion-col size=\"12\" size-sm=\"6\" size-lg=\"4\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header class=\"ion-text-left\">{{ 'HOME.LEGAL_INFORMATION' | translate }}</ion-list-header>\n @for (item of legalInformation; track item.path) {\n <ion-item (click)=\"openMarkdownModal($event, item)\" lines=\"none\" [button]=\"true\">\n <ion-label>\n <p>\n {{ item.title | translate: item.titleArgs }}\n </p>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-col>\n @if (showInstallLinks) {\n <ion-col size=\"12\" size-lg=\"3\" offset-lg=\"9\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.INSTALL_APP' | translate }}</ion-list-header>\n <!-- TODO -->\n </ion-list>\n </ion-col>\n }\n }\n </ion-row>\n </ion-grid>\n }\n }\n\n <!-- image credits -->\n @if (contentCredits | isNotNilOrBlank) {\n <div class=\"content-credits\" [class.floating]=\"!mobile\">\n <ion-text>{{ contentCredits }}</ion-text>\n </div>\n }\n</ion-content>\n", styles: [":host{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .7);--scrollbar-width: 0;--bottom-banner-height: 122px}h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-color:var(--ion-background-color);display:inline-block;padding:15px}ion-content.has-scrollbar{--scrollbar-width: 8px}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:transparent;z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:var(--ion-card-background-color)}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:var(--ion-card-background-color);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{--mdc-icon-button-state-layer-size: 40px;padding:8px;z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{width:100%;--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .5);background-color:var(--ion-card-background-color);min-width:240px;z-index:0;border-radius:4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content .bottom-banner ion-list-header{min-height:30px}ion-content .bottom-banner ion-item{--min-height: 28px}ion-content .bottom-banner ion-item ion-label{margin-top:3px!important;margin-bottom:3px!important}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner.floating{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - var(--bottom-banner-height))}}@media screen and (min-width: 1200px){ion-content ion-col.feed{height:fit-content;max-height:calc(100vh - var(--ion-toolbar-height) - var(--bottom-banner-height) - var(--ion-padding) * 2);padding-inline-end:calc(var(--ion-padding) - 10px);overflow:hidden}ion-content .bottom-banner.floating{position:fixed;left:0;right:0;bottom:0;overflow-y:auto;max-height:var(--bottom-banner-height);margin-top:0}}.content-credits{float:right}.content-credits.floating{float:unset;position:fixed;bottom:0;right:var(--scrollbar-width, 0)}.content-credits ion-text{padding-inline-start:4px;padding-inline-end:4px;font-size:.7rem;background-color:rgba(var(--ion-background-color-rgb),.5);color:rgba(var(--ion-text-color-rgb),.7)}@media screen and (min-width: 992px){.left-border{border-left:1px solid var(--ion-color-medium)}}\n"] }]
35531
+ args: [{ selector: 'app-page-home', changeDetection: ChangeDetectionStrategy.OnPush, animations: [fadeInAnimation, slideUpDownAnimation, slideInAnimation], template: "<app-toolbar [canGoBack]=\"false\" visible-xs visible-sm visible-mobile>\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon [autoHide]=\"autoHideNotificationIcon\"></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button (click)=\"toggleDarkMode()\">\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'sunny' : 'moon'\"></ion-icon>\n </ion-button>\n }\n\n <!-- Change locale button -->\n <ion-button [matMenuTriggerFor]=\"localeMenu\">\n <ion-icon slot=\"start\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n</app-toolbar>\n\n<!-- Change locale menu -->\n<mat-menu #localeMenu=\"matMenu\">\n <ng-template matMenuContent>\n @for (item of locales; track item.key) {\n <button mat-menu-item (click)=\"changeLanguage(item.key)\">\n <!--<mat-icon>{{ currentLocale === item.key ? 'checkmark' : undefined }}</mat-icon>-->\n <ion-label>{{ item.value }}</ion-label>\n </button>\n }\n </ng-template>\n</mat-menu>\n\n<ion-content [ngStyle]=\"contentStyle\" [class.has-scrollbar]=\"hasScrollbar\" no-padding-xs>\n <!-- loading spinner -->\n <div class=\"loading-page\" [class.cdk-visually-hidden]=\"!(loading$ | push)\">\n @if (showSpinner) {\n <div class=\"spinner\">\n <p [innerHTML]=\"'COMMON.LOADING_DOTS' | translate\"></p>\n <div class=\"sk-cube1 sk-cube\"></div>\n <div class=\"sk-cube2 sk-cube\"></div>\n <div class=\"sk-cube4 sk-cube\"></div>\n <div class=\"sk-cube3 sk-cube\"></div>\n </div>\n }\n </div>\n\n <!-- Desktop: translucent top toolbar -->\n @if (!mobile) {\n <div class=\"hidden-xs hidden-sm\">\n <ion-toolbar color=\"traansparent\" translucent>\n <ion-buttons slot=\"start\">\n <ion-menu-toggle>\n <ion-button color=\"light\" fill=\"clear\">\n <ion-icon slot=\"icon-only\" name=\"menu\"></ion-icon>\n </ion-button>\n </ion-menu-toggle>\n </ion-buttons>\n\n <ion-buttons slot=\"end\">\n @if (showNotificationIcon) {\n <app-user-event-notification-icon\n [style]=\"'ion-button'\"\n color=\"secondary\"\n unreadColor=\"tertiary\"\n fill=\"solid\"\n [autoHide]=\"autoHideNotificationIcon\"\n ></app-user-event-notification-icon>\n }\n\n <!-- dark mode (if allow by the environment token) -->\n @if (settings.allowDarkMode) {\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n (click)=\"toggleDarkMode()\"\n [title]=\"'HOME.BTN_DARK_MODE' | translate\"\n >\n <ion-icon slot=\"icon-only\" [name]=\"(darkMode$ | push) ? 'moon' : 'sunny'\"></ion-icon>\n </ion-button>\n }\n <!-- change locale button -->\n <ion-button\n color=\"secondary\"\n fill=\"solid\"\n [matMenuTriggerFor]=\"localeMenu\"\n [title]=\"'SETTINGS.LOCALE' | translate\"\n >\n <ion-icon slot=\"icon-only\" name=\"language\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n </div>\n }\n\n <!-- Install (and upgrade) card -->\n <app-install-upgrade-card [isLogin]=\"isLogin\" [showInstallButton]=\"true\"></app-install-upgrade-card>\n\n @if (!(loading$ | push)) {\n @let hasFeed = hasFeed$ | async;\n <ion-grid class=\"ion-no-padding\">\n <ion-row>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 3 : 0\"></ion-col>\n <ion-col>\n <!-- Welcome card -->\n <ion-card class=\"main welcome ion-padding ion-text-center ion-align-self-center\" @fadeInAnimation>\n <ion-card-header>\n <ion-card-title class=\"ion-text-center\">\n <span *ngIf=\"isWeb\" [innerHTML]=\"'HOME.WELCOME_WEB' | translate: { appName: appName }\"></span>\n <span *ngIf=\"!isWeb\" [innerHTML]=\"'HOME.WELCOME_APP' | translate: { appName: appName }\"></span>\n </ion-card-title>\n <ion-card-subtitle [innerHTML]=\"description\"></ion-card-subtitle>\n </ion-card-header>\n <ion-card-content class=\"ion-no-padding\">\n <ion-text color=\"primary\">\n <img *ngIf=\"logo\" [attr.src]=\"logo\" alt=\"Logo\" />\n </ion-text>\n <!-- register help text -->\n <ion-text *ngIf=\"!isLogin && canRegister\">\n <br />\n <span translate>HOME.REGISTER_HELP</span>\n </ion-text>\n <div class=\"ion-padding-top\">\n <!-- If NOT login -->\n <ng-container *ngIf=\"!isLogin; else loginButtons\">\n <ion-button *ngIf=\"canRegister\" expand=\"block\" color=\"tertiary\" (click)=\"register()\">\n <span translate>HOME.BTN_REGISTER</span>\n </ion-button>\n <ion-button expand=\"block\" color=\"light\" [routerLink]=\"['/']\" (click)=\"login()\">\n <span translate>AUTH.BTN_LOGIN</span>\n </ion-button>\n </ng-container>\n\n <!-- If user login -->\n <ng-template #loginButtons>\n <!-- Feature buttons -->\n <ng-container *rxIf=\"$filteredButtons; let buttons\">\n <ng-container *ngFor=\"let item of buttons\">\n <ion-button\n *ngIf=\"item.path\"\n expand=\"block\"\n color=\"tertiary\"\n [class]=\"item.cssClass\"\n [routerLink]=\"item.path\"\n routerDirection=\"root\"\n >\n <ion-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.icon\" [name]=\"item.icon\"></ion-icon>\n <mat-icon slot=\"start\" class=\"ion-float-start\" *ngIf=\"item.matIcon\">\n {{ item.matIcon }}\n </mat-icon>\n <ion-text>{{ 'HOME.BTN_DATA_ENTRY' | translate: { name: (item.title | translate) } }}</ion-text>\n </ion-button>\n\n <!-- divider -->\n <div *ngIf=\"!item.path && !item.action\" [class]=\"item.cssClass\">\n <ion-label translate>{{ item.title }}&nbsp;</ion-label>\n </div>\n </ng-container>\n\n <!--<p *ngIf=\"buttons.length\" class=\"visible-mobile\">&nbsp;</p>-->\n </ng-container>\n\n <ion-button *ngIf=\"showAccountButton\" expand=\"block\" color=\"secondary\" [routerLink]=\"['/account']\">\n <ion-icon slot=\"start\" class=\"ion-float-start\" name=\"person-circle\"></ion-icon>\n <ion-text translate>HOME.BTN_MY_ACCOUNT</ion-text>\n </ion-button>\n\n <p hidden-xs hidden-sm hidden-mobile>\n <ion-text\n [innerHTML]=\"'HOME.NOT_THIS_ACCOUNT_QUESTION' | translate: { displayName: accountName }\"\n ></ion-text>\n <br />\n <ion-text>\n <a href=\"#\" (click)=\"logout($event)\">\n <span translate>HOME.BTN_DISCONNECT</span>\n </a>\n </ion-text>\n </p>\n </ng-template>\n </div>\n </ion-card-content>\n </ion-card>\n </ion-col>\n <ion-col size=\"0\" [sizeXl]=\"hasFeed ? 1 : 0\"></ion-col>\n <ion-col [class.feed]=\"hasFeed\" [size]=\"hasFeed ? 12 : 0\" [sizeXl]=\"hasFeed ? 4 : 0\">\n @if (showFeed) {\n @if (mobile) {\n <app-feed\n #feed\n shape=\"round\"\n [urls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @fadeInAnimation\n ></app-feed>\n } @else {\n <app-feed\n #feed\n shape=\"round\"\n [debug]=\"false\"\n [urls]=\"feedUrls\"\n [maxAgeInMonths]=\"feedMaxAgeInMonths\"\n [maxContentLength]=\"feedMaxContentLength\"\n (ngInit)=\"initFeed(feed)\"\n @slideInAnimation\n @fadeInAnimation\n ></app-feed>\n }\n }\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Page history -->\n @let pageHistory = isLogin && (pageHistory$ | push);\n @if (pageHistory | isNotEmptyArray) {\n <ion-grid class=\"history-container ion-align-self-center\">\n <ion-row>\n @for (page of pageHistory; track page.path) {\n <ion-col size=\"12\" size-xl=\"\" class=\"ion-text-center\">\n <ion-card class=\"ion-align-self-start ion-text-start\" @fadeInAnimation>\n <ion-card-header class=\"ion-no-padding\">\n <!-- top bar -->\n <ion-card-subtitle>\n <button\n type=\"button\"\n tabindex=\"-1\"\n (click)=\"removePageHistory(page.path)\"\n mat-icon-button\n class=\"ion-float-start ion-no-margin close-button\"\n >\n <mat-icon>close</mat-icon>\n </button>\n <ion-label [innerHTML]=\"page.subtitle | translate\"></ion-label>\n <ion-text class=\"ion-float-end\" [title]=\"page.time | dateFormat: { time: true }\">\n <small>\n <ion-icon name=\"time-outline\"></ion-icon>\n &nbsp;{{ page.time | dateFromNow }}\n </small>\n &nbsp;\n </ion-text>\n </ion-card-subtitle>\n\n <!-- main page -->\n <ion-card-title class=\"ion-no-margin ion-no-padding\">\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"page.path\"\n routerDirection=\"root\"\n lines=\"none\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"page.icon\" slot=\"start\" [name]=\"page.icon\"></ion-icon>\n <mat-icon *ngIf=\"page.matIcon\" slot=\"start\">{{ page.matIcon }}</mat-icon>\n\n <ion-label color=\"primary\" [innerHTML]=\"page.title\"></ion-label>\n </ion-item>\n </ion-card-title>\n </ion-card-header>\n\n <!-- children pages -->\n @if (page.children | isNotEmptyArray) {\n <ion-card-content class=\"ion-no-padding ion-padding-start\">\n <ion-list class=\"ion-no-padding\">\n @for (childPage of page.children; track childPage.path) {\n <ion-item\n detail=\"true\"\n tappable\n class=\"text-1x\"\n [routerLink]=\"childPage.path\"\n routerDirection=\"root\"\n >\n <!-- page icon-->\n <ion-icon *ngIf=\"childPage.icon\" slot=\"start\" color=\"dark\" [name]=\"childPage.icon\"></ion-icon>\n <mat-icon *ngIf=\"childPage.matIcon\" slot=\"start\" color=\"dark\">\n {{ childPage.matIcon }}\n </mat-icon>\n\n <ion-label color=\"dark\" [innerHTML]=\"childPage.title\"></ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-card-content>\n }\n </ion-card>\n </ion-col>\n }\n </ion-row>\n </ion-grid>\n }\n\n <!-- Bottom banner -->\n @if (showPartnerBanner || showLegalInformation) {\n <ion-grid\n class=\"bottom-banner ion-text-center\"\n [class.floating]=\"!mobile && (pageHistory | isEmptyArray)\"\n @fadeInAnimation\n >\n <ion-row>\n <!-- partners logos -->\n @if (showPartnerBanner) {\n <ion-col size=\"12\" [sizeLg]=\"partnerBannerSize\" class=\"ion-text-center\">\n @if (showLegalInformation) {\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.PARTNERS' | translate }}</ion-list-header>\n </ion-list>\n }\n <!-- partners -->\n @for (item of $partners | async; track $index) {\n <a href=\"{{ item.siteUrl }}\">\n <img class=\"logo\" src=\"{{ item.logo }}\" alt=\"{{ item.label }}\" [title]=\"item.label\" />\n </a>\n }\n </ion-col>\n }\n\n <!-- legal information -->\n @if (showLegalInformation) {\n <ion-col size=\"12\" size-sm=\"6\" size-lg=\"4\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header class=\"ion-text-left\">{{ 'HOME.LEGAL_INFORMATION' | translate }}</ion-list-header>\n @for (item of legalInformation; track item.path) {\n <ion-item (click)=\"openMarkdownModal($event, item)\" lines=\"none\" [button]=\"true\">\n <ion-label>\n <p>\n {{ item.title | translate: item.titleArgs }}\n </p>\n </ion-label>\n </ion-item>\n }\n </ion-list>\n </ion-col>\n @if (showInstallLinks) {\n <ion-col size=\"12\" size-lg=\"3\" offset-lg=\"9\" [class.left-border]=\"showPartnerBanner\">\n <ion-list class=\"legal-info\">\n <ion-list-header>{{ 'HOME.INSTALL_APP' | translate }}</ion-list-header>\n <!-- TODO -->\n </ion-list>\n </ion-col>\n }\n }\n </ion-row>\n </ion-grid>\n }\n }\n\n <!-- image credits -->\n @if (contentCredits | isNotNilOrBlank) {\n <div class=\"content-credits\" [class.floating]=\"!mobile\">\n <ion-text>{{ contentCredits }}</ion-text>\n </div>\n }\n</ion-content>\n", styles: [":host{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .7);--scrollbar-width: 0;--bottom-banner-height: 122px}h1,h2,h3,h4,h5{white-space:normal}.center,ion-content ion-card.welcome img{text-align:center;display:inline-block}ion-content{--background: transparent;background-size:cover;-webkit-background-size:cover;-moz-background-size:cover;-o-background-size:cover;background-color:var(--ion-background-color);display:inline-block;padding:15px}ion-content.has-scrollbar{--scrollbar-width: 8px}ion-content .loading-page{display:block;height:100%;width:100%;position:absolute;background-color:transparent;z-index:5;transition:background .5s linear;-webkit-transition:background 1.5s linear}ion-content .loading-page.hidden{background-color:rgba(var(--ion-color-primary),0)}ion-content ion-card{z-index:9;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content ion-card ion-card-header{display:block}ion-content ion-card ion-card-header ion-card-subtitle,ion-content ion-card ion-card-header ion-card-title{display:block;width:100%}ion-content ion-card.main{min-width:240px;max-width:400px;margin-left:auto;margin-right:auto}ion-content ion-card.welcome{background-color:var(--ion-card-background-color)}ion-content ion-card.welcome button{margin-top:16px}ion-content ion-card.welcome img{max-width:250px}ion-content .history-container{margin-left:auto;margin-right:auto}ion-content .history-container ion-card{display:inline-block;box-sizing:border-box;z-index:8;min-width:240px;max-width:400px;width:100%;margin-left:auto;margin-right:auto;background-color:var(--ion-card-background-color);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}ion-content .history-container ion-card ion-card-header ion-card-subtitle{height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle button[float-start]{--mdc-icon-button-state-layer-size: 40px;padding:8px;z-index:99;margin:0}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label{line-height:40px}ion-content .history-container ion-card ion-card-header ion-card-subtitle ion-label[float-end]{padding-right:16px;font-weight:400}ion-content .history-container ion-card ion-card-header ion-card-title ion-item{width:100%;--ion-item-background: $transparent;--ion-item-icon-color: var(--ion-color-primary);--ion-item-text-color: var(--ion-color-primary)}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-header ion-card-title ion-item mat-icon[slot=start]{margin-right:unset;margin-inline-end:16px!important;color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor}ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-text,ion-content .history-container ion-card ion-card-header ion-card-title ion-item ion-label{color:var(--ion-item-text-color)!important;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}ion-content .history-container ion-card ion-card-content ion-list{--ion-item-background: var(--ion-color-transparent)}ion-content .history-container ion-card ion-card-content ion-list ion-item{--ion-item-icon-color: var(--ion-color-dark);--ion-item-text-color: var(--ion-color-dark)}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-icon[slot=start],ion-content .history-container ion-card ion-card-content ion-list ion-item mat-icon[slot=start]{color:var(--ion-item-icon-color)!important;fill:currentColor;stroke:currentColor;margin-right:unset;margin-inline-end:16px!important}ion-content .history-container ion-card ion-card-content ion-list ion-item ion-text,ion-content .history-container ion-card ion-card-content ion-list ion-item ion-label{color:var(--ion-item-text-color)!important}ion-content .bottom-banner{--ion-card-background-color: rgba(var(--ion-background-color-rgb), .5);background-color:var(--ion-card-background-color);min-width:240px;z-index:0;border-radius:4px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);margin:16px}ion-content .bottom-banner img.logo{max-height:50px;margin-left:8px}ion-content .bottom-banner ion-list-header{min-height:30px}ion-content .bottom-banner ion-item{--min-height: 28px}ion-content .bottom-banner ion-item ion-label{margin-top:3px!important;margin-bottom:3px!important}ion-content ion-text{text-align:center}@media screen and (max-width: 575px){ion-content ion-card{min-width:160px;width:calc(100% - 20px);max-width:100%;margin-left:10px;margin-right:10px}ion-content ion-card h1{margin-top:0;font-size:20px}ion-content ion-card ion-card-content ion-button{margin-top:8px}ion-content .bottom-banner.floating{display:block;position:unset}ion-content .bottom-banner img.logo{max-height:30px}}@media screen and (max-width: 767px) and (min-width: 576px){ion-content{min-height:555px}ion-content .bottom-banner img.logo{max-height:40px}}@media screen and (min-width: 768px){ion-content{min-height:calc(100% - var(--bottom-banner-height))}}@media screen and (min-width: 1200px){ion-content ion-col.feed{height:fit-content;max-height:calc(100vh - var(--ion-toolbar-height) - var(--bottom-banner-height) - var(--ion-padding) * 2);padding-inline-end:calc(var(--ion-padding) - 10px);overflow:hidden}ion-content .bottom-banner.floating{position:fixed;left:0;right:0;bottom:0;overflow-y:auto;max-height:var(--bottom-banner-height);margin-top:0}}.content-credits{float:right}.content-credits.floating{float:unset;position:fixed;bottom:0;right:var(--scrollbar-width, 0)}.content-credits ion-text{padding-inline-start:4px;padding-inline-end:4px;font-size:.7rem;background-color:rgba(var(--ion-background-color-rgb),.5);color:rgba(var(--ion-text-color-rgb),.7)}@media screen and (min-width: 992px){.left-border{border-left:1px solid var(--ion-color-medium)}}\n"] }]
34651
35532
  }], ctorParameters: () => [{ type: AccountService }, { type: i2$1.ModalController }, { type: i1$1.TranslateService }, { type: i2$1.ToastController }, { type: ConfigService }, { type: PlatformService }, { type: i0.ChangeDetectorRef }, { type: NetworkService }, { type: LocalSettingsService }, { type: Environment, decorators: [{
34652
35533
  type: Inject,
34653
35534
  args: [ENVIRONMENT]
@@ -34864,11 +35745,11 @@ class FeedPage {
34864
35745
  this.destroySubject.complete();
34865
35746
  }
34866
35747
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedPage, deps: [{ token: LocalSettingsService }, { token: i0.ChangeDetectorRef }, { token: APP_FEED_SERVICE, optional: true }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
34867
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: FeedPage, selector: "app-feed-page", inputs: { debug: "debug", title: "title", feedUrls: "feedUrls", feedItemId: "feedItemId" }, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>{{ title | translate }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <div class=\"ion-padding\">\n @if (loadingSubject | async) {\n <ion-spinner></ion-spinner>\n } @else {\n <app-feed [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [feedUrls]=\"feedUrls\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n }\n </div>\n</ion-content>\n", dependencies: [{ kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "feedUrls", "maxAgeInMonths", "maxContentLength"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
35748
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: FeedPage, selector: "app-feed-page", inputs: { debug: "debug", title: "title", feedUrls: "feedUrls", feedItemId: "feedItemId" }, ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>{{ title | translate }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <div class=\"ion-padding\">\n @if (loadingSubject | async) {\n <ion-spinner></ion-spinner>\n } @else {\n <app-feed [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [urls]=\"feedUrls\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n }\n </div>\n</ion-content>\n", dependencies: [{ kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }, { kind: "pipe", type: i3$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
34868
35749
  }
34869
35750
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedPage, decorators: [{
34870
35751
  type: Component,
34871
- args: [{ selector: 'app-feed-page', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>{{ title | translate }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <div class=\"ion-padding\">\n @if (loadingSubject | async) {\n <ion-spinner></ion-spinner>\n } @else {\n <app-feed [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [feedUrls]=\"feedUrls\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n }\n </div>\n</ion-content>\n" }]
35752
+ args: [{ selector: 'app-feed-page', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>{{ title | translate }}</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <div class=\"ion-padding\">\n @if (loadingSubject | async) {\n <ion-spinner></ion-spinner>\n } @else {\n <app-feed [showHeader]=\"false\"\n [showReadMoreButton]=\"false\"\n [urls]=\"feedUrls\"\n cardColor=\"light\"\n [debug]=\"debug\"\n ></app-feed>\n }\n </div>\n</ion-content>\n" }]
34872
35753
  }], ctorParameters: () => [{ type: LocalSettingsService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
34873
35754
  type: Optional
34874
35755
  }, {
@@ -44334,528 +45215,174 @@ class AppEntityEditor extends AppTabEditor {
44334
45215
  // can be overwritten by subclasses
44335
45216
  }
44336
45217
  computeUsageMode(data) {
44337
- // can be overwritten by subclasses
44338
- // Using local settings default value
44339
- return this.settings.usageMode;
44340
- }
44341
- waitWhilePending(opts) {
44342
- return AppFormUtils.waitWhilePending(this, opts);
44343
- }
44344
- async getValue() {
44345
- const json = await this.getJsonValueToSave();
44346
- const res = new this.dataType();
44347
- res.fromObject(json);
44348
- return res;
44349
- }
44350
- getJsonValueToSave() {
44351
- return Promise.resolve(this.form.value);
44352
- }
44353
- /**
44354
- * Compute the title
44355
- *
44356
- * @param data
44357
- */
44358
- async updateTitle(data) {
44359
- data = data || this.data;
44360
- const title = await this.computeTitle(data);
44361
- this.titleSubject.next(title);
44362
- // If NOT data, then add to page history
44363
- if (!this.isNewData) {
44364
- const page = await this.computePageHistory(title);
44365
- if (page)
44366
- return this.addToPageHistory(page);
44367
- }
44368
- }
44369
- async addToPageHistory(page, opts) {
44370
- if (!page)
44371
- return; // Skip
44372
- return this.settings.addToPageHistory(page, {
44373
- removePathQueryParams: true,
44374
- removeTitleSmallTag: true,
44375
- emitEvent: false,
44376
- ...opts,
44377
- });
44378
- }
44379
- async computePageHistory(title) {
44380
- if (this.debug)
44381
- console.debug('[entity-editor] Computing page history, using url: ' + this.router.url);
44382
- return {
44383
- title,
44384
- path: this.router.url,
44385
- };
44386
- }
44387
- async removePageHistory(opts) {
44388
- return this.settings.removePageHistory(this.router.url, opts);
44389
- }
44390
- computePageUrl(id) {
44391
- const parentUrl = this.getParentPageUrl();
44392
- return parentUrl && `${parentUrl}/${id}`;
44393
- }
44394
- getParentPageUrl(withQueryParams) {
44395
- let parentUrl = this.defaultBackHref;
44396
- // Remove query params
44397
- if (withQueryParams !== true && parentUrl && parentUrl.indexOf('?') !== -1) {
44398
- parentUrl = parentUrl.substring(0, parentUrl.indexOf('?'));
44399
- }
44400
- return parentUrl;
44401
- }
44402
- listenChanges(id, opts) {
44403
- return this.dataService.listenChanges(this.data.id, {
44404
- // Make sure to skip cache, because NOT the full graph will be fetched by subscription
44405
- // When a changes occur, we call reload() to force refetching the full graph
44406
- fetchPolicy: 'no-cache',
44407
- ...opts,
44408
- });
44409
- }
44410
- markForCheck() {
44411
- this.cd.markForCheck();
44412
- }
44413
- /**
44414
- * Update the route, without reloading the component
44415
- *
44416
- * @param data
44417
- * @param queryParams
44418
- * @protected
44419
- */
44420
- async updateRoute(data, queryParams) {
44421
- data = data || this.data;
44422
- if (!data || !this.route)
44423
- return false;
44424
- const currId = this.route.snapshot.paramMap.get(this.pathIdAttribute);
44425
- const futureId = isNotNil(data.id) ? data.id.toString() : 'new';
44426
- if (queryParams) {
44427
- this.queryParams = {
44428
- ...this.queryParams,
44429
- ...queryParams,
44430
- };
44431
- }
44432
- if (currId === futureId) {
44433
- // Update queryParam only (not the path)
44434
- // /!\ We should get query Params from the updated route, and not from snapshot that can be outdated
44435
- const actualQueryParams = await firstNotNilPromise(this.route.queryParams);
44436
- if (!equals(actualQueryParams, this.queryParams)) {
44437
- if (this.debug)
44438
- console.debug(`${this._logPrefix}Updating route using queryParams: `, this.queryParams);
44439
- return await this.router.navigate(['.'], {
44440
- relativeTo: this.route,
44441
- replaceUrl: false,
44442
- queryParams: this.queryParams,
44443
- state: { animated: false },
44444
- });
44445
- }
44446
- }
44447
- else {
44448
- const path = this.computePageUrl(isNotNil(data.id) ? data.id : 'new');
44449
- const commands = path && typeof path === 'string' ? path.split('/').slice(1) : path;
44450
- if (isNotEmptyArray(commands)) {
44451
- // Current route was '/new' => change to '/new?id=:id' (to allow CustomReuseStrategy to detect that route can be reused)
44452
- if (currId === 'new' && this.queryParams[this.pathIdAttribute] !== futureId) {
44453
- if (this.debug)
44454
- console.debug(`${this._logPrefix}Updating route /new into /:id, using queryParams: `, queryParams);
44455
- this.queryParams[this.pathIdAttribute] = futureId;
44456
- const res = await this.router.navigate(['.'], {
44457
- relativeTo: this.route,
44458
- replaceUrl: true,
44459
- queryParams: this.queryParams,
44460
- state: { animated: false },
44461
- });
44462
- if (!res)
44463
- return false;
44464
- }
44465
- if (this.debug)
44466
- console.debug(`${this._logPrefix}Updating route to: ` + path);
44467
- return await this.router.navigate(commands, {
44468
- replaceUrl: true,
44469
- queryParams: this.queryParams,
44470
- state: { animated: false },
44471
- });
44472
- }
44473
- return Promise.reject('Missing page URL');
44474
- }
44475
- }
44476
- /* -- private functions -- */
44477
- /**
44478
- * Open the first tab that is invalid
44479
- */
44480
- openFirstInvalidTab() {
44481
- const invalidTabIndex = this.getFirstInvalidTabIndex();
44482
- if (invalidTabIndex !== -1 && this.selectedTabIndex !== invalidTabIndex) {
44483
- this.selectedTabIndex = invalidTabIndex;
44484
- this.markForCheck();
44485
- }
44486
- }
44487
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppEntityEditor, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
44488
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: AppEntityEditor, usesInheritance: true, ngImport: i0 });
44489
- }
44490
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppEntityEditor, decorators: [{
44491
- type: Directive
44492
- }], ctorParameters: () => [{ type: i0.Injector }, { type: undefined }, { type: undefined }, { type: AppEditorOptions, decorators: [{
44493
- type: Optional
44494
- }] }] });
44495
-
44496
- var PersonFilter_1;
44497
- // @dynamic
44498
- let PersonFilter = class PersonFilter extends EntityFilter {
44499
- static { PersonFilter_1 = this; }
44500
- static fromObject;
44501
- static searchFilter(source) {
44502
- return source && PersonFilter_1.fromObject(source).asFilterFn();
44503
- }
44504
- email;
44505
- pubkey;
44506
- searchText;
44507
- statusIds;
44508
- userProfiles;
44509
- includedIds;
44510
- excludedIds;
44511
- searchAttribute;
44512
- searchAttributes;
44513
- constructor() {
44514
- super(PersonFilter_1.TYPENAME);
44515
- }
44516
- fromObject(source, opts) {
44517
- super.fromObject(source, opts);
44518
- this.email = source.email;
44519
- this.pubkey = source.pubkey;
44520
- this.searchText = source.searchText;
44521
- this.statusIds = source.statusIds || (isNotNil(source.statusId) ? [source.statusId] : undefined);
44522
- this.userProfiles = source.userProfiles;
44523
- this.excludedIds = source.excludedIds;
44524
- this.searchAttribute = source.searchAttribute;
44525
- this.searchAttributes = source.searchAttributes;
44526
- }
44527
- asObject(opts) {
44528
- const target = super.asObject(opts);
44529
- target.email = this.email;
44530
- target.pubkey = this.pubkey;
44531
- target.searchText = this.searchText;
44532
- target.statusIds = this.statusIds;
44533
- target.userProfiles = this.userProfiles;
44534
- target.excludedIds = this.excludedIds;
44535
- target.searchAttribute = this.searchAttribute;
44536
- target.searchAttributes = this.searchAttributes;
44537
- return target;
44538
- }
44539
- buildFilter() {
44540
- const filterFns = super.buildFilter();
44541
- // Filter by status
44542
- if (isNotEmptyArray(this.statusIds)) {
44543
- filterFns.push((e) => this.statusIds.includes(e.statusId));
44544
- }
44545
- // Filter included ids
44546
- const includedIds = this.includedIds;
44547
- if (isNotEmptyArray(includedIds)) {
44548
- filterFns.push((e) => isNotNil(e.id) && includedIds.includes(e.id));
44549
- }
44550
- // Filter excluded ids
44551
- const excludedIds = this.excludedIds;
44552
- if (isNotEmptyArray(excludedIds)) {
44553
- filterFns.push((e) => isNil(e.id) || !excludedIds.includes(e.id));
44554
- }
44555
- // Search text
44556
- const searchTextFilter = EntityUtils.searchTextFilter(this.searchAttribute || this.searchAttributes || ['lastName', 'firstName', 'department.name'], this.searchText);
44557
- if (searchTextFilter)
44558
- filterFns.push(searchTextFilter);
44559
- return filterFns;
44560
- }
44561
- };
44562
- PersonFilter = PersonFilter_1 = __decorate([
44563
- EntityClass({ typename: 'PersonFilterVO' })
44564
- ], PersonFilter);
44565
-
44566
- const MessageTypes = {
44567
- INBOX_MESSAGE: 'INBOX_MESSAGE',
44568
- EMAIL: 'EMAIL',
44569
- FEED: 'FEED',
44570
- };
44571
- const MessageTypeList = Object.freeze([
44572
- {
44573
- id: MessageTypes.INBOX_MESSAGE,
44574
- icon: 'notifications',
44575
- label: 'SOCIAL.MESSAGE.TYPE_ENUM.INBOX_MESSAGE',
44576
- },
44577
- {
44578
- id: MessageTypes.EMAIL,
44579
- icon: 'mail',
44580
- label: 'SOCIAL.MESSAGE.TYPE_ENUM.EMAIL',
44581
- },
44582
- {
44583
- id: MessageTypes.FEED,
44584
- icon: 'megaphone',
44585
- label: 'SOCIAL.MESSAGE.TYPE_ENUM.FEED',
44586
- },
44587
- ]);
44588
- // @dynamic
44589
- let Message = class Message extends Entity {
44590
- static fromObject;
44591
- type;
44592
- issuer;
44593
- recipients;
44594
- recipientFilter;
44595
- subject;
44596
- body;
44597
- constructor() {
44598
- super();
44599
- }
44600
- fromObject(source, opts) {
44601
- super.fromObject(source, opts);
44602
- this.type = source.type;
44603
- this.issuer = source.issuer && Person.fromObject(source.issuer);
44604
- this.recipients = source.recipients && source.recipients.map(Person.fromObject);
44605
- this.recipientFilter = (source.recipientFilter && PersonFilter.fromObject(source.recipientFilter)) || undefined;
44606
- this.subject = source.subject;
44607
- this.body = source.body;
44608
- }
44609
- asObject(opts) {
44610
- const target = super.asObject(opts);
44611
- target.recipients = this.recipients && this.recipients.map((p) => p.asObject(opts));
44612
- target.recipientFilter = (this.recipientFilter && this.recipientFilter.asObject(opts)) || undefined;
44613
- return target;
44614
- }
44615
- };
44616
- Message = __decorate([
44617
- EntityClass({ typename: 'MessageVO' })
44618
- ], Message);
44619
- // @dynamic
44620
- let MessageFilter = class MessageFilter extends EntityFilter {
44621
- static fromObject;
44622
- fromObject(source, opts) {
44623
- super.fromObject(source, opts);
44624
- }
44625
- buildFilter() {
44626
- return super.buildFilter();
44627
- }
44628
- };
44629
- MessageFilter = __decorate([
44630
- EntityClass({ typename: 'MessageFilterVO' })
44631
- ], MessageFilter);
44632
-
44633
- class MessageForm extends AppForm {
44634
- formBuilder;
44635
- cd;
44636
- mobile;
44637
- suggestFn;
44638
- subjectMinLength = 5;
44639
- subjectMaxLength = 255;
44640
- bodyMaxLength = 2000;
44641
- bodyAutoHeight = true;
44642
- canSelectType = false;
44643
- canRecipientFilter = false;
44644
- recipientFilterCount = 0;
44645
- types = MessageTypeList;
44646
- constructor(injector, formBuilder, cd) {
44647
- super(injector);
44648
- this.formBuilder = formBuilder;
44649
- this.cd = cd;
44650
- this.mobile = this.settings.mobile;
44651
- }
44652
- ngOnInit() {
44653
- this.setForm(this.formBuilder.group({
44654
- type: [MessageTypes.INBOX_MESSAGE, Validators.required],
44655
- recipients: [null, Validators.required],
44656
- recipientFilter: [null],
44657
- subject: [
44658
- null,
44659
- this.subjectMaxLength
44660
- ? Validators.compose([Validators.required, Validators.minLength(this.subjectMinLength), Validators.maxLength(this.subjectMaxLength)])
44661
- : Validators.required,
44662
- ],
44663
- body: [null, this.bodyMaxLength ? Validators.compose([Validators.maxLength(this.bodyMaxLength)]) : Validators.required],
44664
- }));
44665
- this.registerSubscription(this._form
44666
- .get('type')
44667
- .valueChanges.pipe(filter(isNotNil))
44668
- .subscribe((type) => this.updateFormGroup(this._form, { type })));
44669
- // Person combo
44670
- const personAttributes = this.settings.getFieldDisplayAttributes('person', ['lastName', 'firstName', 'department.name']);
44671
- this.registerAutocompleteField('recipients', {
44672
- showAllOnFocus: false,
44673
- suggestFn: this.suggestFn,
44674
- filter: {
44675
- statusIds: [StatusIds.TEMPORARY, StatusIds.ENABLE],
44676
- },
44677
- attributes: personAttributes,
44678
- columnNames: personAttributes.map((attr) => `USER.${changeCaseToUnderscore(attr).toUpperCase()}`),
44679
- displayWith: PersonUtils.personToString,
44680
- multiple: true,
44681
- mobile: this.mobile,
44682
- });
44683
- }
44684
- isSamePerson(o1, o2) {
44685
- return EntityUtils.equals(o1, o2, 'id');
44686
- }
44687
- updateFormGroup(formGroup, opts) {
44688
- console.debug('[message-form] Updating form group...', opts);
44689
- // Recipient validator
44690
- const recipientsRequired = toBoolean(opts?.recipientRequired, opts?.type !== MessageTypes.FEED);
44691
- {
44692
- const control = formGroup.get('recipients');
44693
- if (recipientsRequired) {
44694
- if (!control.hasValidator(Validators.required)) {
44695
- control.addValidators(Validators.required);
44696
- }
44697
- control.enable();
44698
- }
44699
- else {
44700
- if (control.hasValidator(Validators.required)) {
44701
- control.removeValidators(Validators.required);
44702
- }
44703
- control.disable();
44704
- }
44705
- }
44706
- // Recipient filter validator
44707
- const recipientFilterRequired = this.canRecipientFilter && !recipientsRequired;
44708
- {
44709
- const control = formGroup.get('recipientFilter');
44710
- if (recipientFilterRequired) {
44711
- if (!control.hasValidator(Validators.required)) {
44712
- control.addValidators(Validators.required);
44713
- }
44714
- control.enable();
44715
- }
44716
- else {
44717
- if (control.hasValidator(Validators.required)) {
44718
- control.removeValidators(Validators.required);
44719
- }
44720
- control.disable();
44721
- }
44722
- }
44723
- formGroup.updateValueAndValidity();
45218
+ // can be overwritten by subclasses
45219
+ // Using local settings default value
45220
+ return this.settings.usageMode;
44724
45221
  }
44725
- markForCheck() {
44726
- this.cd.markForCheck();
45222
+ waitWhilePending(opts) {
45223
+ return AppFormUtils.waitWhilePending(this, opts);
44727
45224
  }
44728
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageForm, deps: [{ token: i0.Injector }, { token: i1$3.UntypedFormBuilder }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
44729
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.13", type: MessageForm, selector: "app-message-form", inputs: { suggestFn: "suggestFn", subjectMinLength: "subjectMinLength", subjectMaxLength: "subjectMaxLength", bodyMaxLength: "bodyMaxLength", bodyAutoHeight: "bodyAutoHeight", canSelectType: "canSelectType", canRecipientFilter: "canRecipientFilter", recipientFilterCount: "recipientFilterCount" }, usesInheritance: true, ngImport: i0, template: "<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n <mat-form-field *ngIf=\"canSelectType\">\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n ></mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n <mat-error *ngIf=\"form.controls.subject.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('minlength')\">\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-hint *ngIf=\"subjectMaxLength\" align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n </mat-form-field>\n\n <!-- Body -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n <mat-error *ngIf=\"form.controls.body.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n <mat-hint *ngIf=\"bodyMaxLength\" align=\"end\">\n {{ 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength } }}\n </mat-hint>\n </mat-form-field>\n</form>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}\n"], dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "directive", type: i1$3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$3.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$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i3.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "directive", type: i1$4.CdkTextareaAutosize, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMinRows", "cdkAutosizeMaxRows", "cdkTextareaAutosize", "placeholder"], exportAs: ["cdkTextareaAutosize"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: MatChipsField, selector: "mat-chips-field", inputs: ["equals", "logPrefix", "formControl", "formControlName", "floatLabel", "placeholder", "suggestFn", "required", "mobile", "readonly", "clearable", "debounceTime", "displaySeparator", "displayWith", "displayAttributes", "displayColumnSizes", "displayColumnNames", "highlightAccent", "showAllOnFocus", "showPanelOnFocus", "autofocus", "config", "i18nPrefix", "noResultMessage", "panelClass", "panelWidth", "disableRipple", "matAutocompletePosition", "itemSize", "fetchMoreThreshold", "suggestLengthThreshold", "showLoadingSpinner", "chipColor", "debug", "applyImplicitValue", "dropButtonTitle", "clearButtonTitle", "trimSearchText", "hideRequiredMarker", "colSizes", "separatorKeysCodes", "appearance", "subscriptSizing", "class", "filter", "tabindex", "items"], outputs: ["click", "blur", "focus", "dropButtonClick", "keydown.escape", "keyup.enter"] }, { kind: "directive", type: AutoResizeDirective, selector: "textarea[cdkTextareaAutosize]", inputs: ["cdkAutosizeMaxRows", "cdkAutosizeMinRows"] }, { kind: "component", type: i11$1.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "component", type: i11$1.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }, { kind: "pipe", type: FormGetValuePipe, name: "formGetValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
44730
- }
44731
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageForm, decorators: [{
44732
- type: Component,
44733
- args: [{ selector: 'app-message-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<form [formGroup]=\"form\" class=\"form-container\">\n <!-- type -->\n <mat-form-field *ngIf=\"canSelectType\">\n <mat-label>{{ 'SOCIAL.MESSAGE.TYPE' | translate }}</mat-label>\n <mat-select formControlName=\"type\">\n @for (item of types; track item.id) {\n @if (canRecipientFilter || item.id !== 'FEED') {\n <mat-option [value]=\"item.id\">\n <ion-icon [name]=\"item.icon\"></ion-icon>\n {{ item.label | translate }}\n </mat-option>\n }\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Recipients -->\n @if ((form | formGetValue: 'type') !== 'FEED') {\n <mat-chips-field\n formControlName=\"recipients\"\n chipColor=\"accent\"\n [placeholder]=\"'SOCIAL.MESSAGE.RECIPIENTS' | translate\"\n [config]=\"autocompleteFields.recipients\"\n [equals]=\"isSamePerson\"\n ></mat-chips-field>\n } @else {\n <mat-form-field>\n <input matInput hidden formControlName=\"recipients\" type=\"text\" />\n <mat-chip-grid>\n <mat-chip-row>\n {{ 'SOCIAL.MESSAGE.RECIPIENT_FILTER_COUNT' | translate: { count: recipientFilterCount } }}\n </mat-chip-row>\n </mat-chip-grid>\n </mat-form-field>\n }\n\n <!-- Subject -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.SUBJECT' | translate }}</mat-label>\n <input matInput type=\"text\" formControlName=\"subject\" autocomplete=\"off\" required />\n <mat-error *ngIf=\"form.controls.subject.hasError('required')\" translate>ERROR.FIELD_REQUIRED</mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('minlength')\">\n {{ 'ERROR.FIELD_MIN_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-error *ngIf=\"form.controls.subject.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH_COMPACT' | translate }}\n </mat-error>\n <mat-hint *ngIf=\"subjectMaxLength\" align=\"end\">\n {{\n 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.subject.value?.length || 0, max: subjectMaxLength }\n }}\n </mat-hint>\n </mat-form-field>\n\n <!-- Body -->\n <mat-form-field>\n <mat-label>{{ 'SOCIAL.MESSAGE.BODY_HELP' | translate }}</mat-label>\n <textarea\n matInput\n type=\"text\"\n formControlName=\"body\"\n [class.fixed-height]=\"!bodyAutoHeight\"\n [cdkTextareaAutosize]=\"bodyAutoHeight\"\n (keydown.control.enter)=\"doSubmit($event)\"\n ></textarea>\n <mat-error *ngIf=\"form.controls.body.hasError('maxlength')\">\n {{ 'ERROR.FIELD_MAX_LENGTH' | translate: form.controls.body.errors.maxlength }}\n </mat-error>\n <mat-hint *ngIf=\"bodyMaxLength\" align=\"end\">\n {{ 'INFO.TEXT_PROGRESS' | translate: { current: form.controls.body.value?.length || 0, max: bodyMaxLength } }}\n </mat-hint>\n </mat-form-field>\n</form>\n", styles: ["textarea.fixed-height{height:11.5em}textarea{min-height:11.5em}\n"] }]
44734
- }], ctorParameters: () => [{ type: i0.Injector }, { type: i1$3.UntypedFormBuilder }, { type: i0.ChangeDetectorRef }], propDecorators: { suggestFn: [{
44735
- type: Input
44736
- }], subjectMinLength: [{
44737
- type: Input
44738
- }], subjectMaxLength: [{
44739
- type: Input
44740
- }], bodyMaxLength: [{
44741
- type: Input
44742
- }], bodyAutoHeight: [{
44743
- type: Input
44744
- }], canSelectType: [{
44745
- type: Input
44746
- }], canRecipientFilter: [{
44747
- type: Input
44748
- }], recipientFilterCount: [{
44749
- type: Input
44750
- }] } });
44751
-
44752
- class MessageModal {
44753
- settings;
44754
- viewCtrl;
44755
- accountService;
44756
- cd;
44757
- mobile;
44758
- suggestFn;
44759
- data;
44760
- canSelectType;
44761
- canRecipientFilter;
44762
- recipientFilterCount;
44763
- form;
44764
- constructor(settings, viewCtrl, accountService, cd) {
44765
- this.settings = settings;
44766
- this.viewCtrl = viewCtrl;
44767
- this.accountService = accountService;
44768
- this.cd = cd;
44769
- this.mobile = this.settings.mobile;
45225
+ async getValue() {
45226
+ const json = await this.getJsonValueToSave();
45227
+ const res = new this.dataType();
45228
+ res.fromObject(json);
45229
+ return res;
44770
45230
  }
44771
- ngOnInit() {
44772
- this.canSelectType = toBoolean(this.canSelectType, this.accountService.isAdmin());
45231
+ getJsonValueToSave() {
45232
+ return Promise.resolve(this.form.value);
44773
45233
  }
44774
- ngAfterViewInit() {
44775
- setTimeout(() => {
44776
- this.form.markAsReady({ emitEvent: false });
44777
- if (this.data) {
44778
- this.data.type = this.data.type || MessageTypes.INBOX_MESSAGE;
44779
- this.form.setValue(this.data);
44780
- }
44781
- this.form.markAsLoaded();
44782
- this.form.enable();
45234
+ /**
45235
+ * Compute the title
45236
+ *
45237
+ * @param data
45238
+ */
45239
+ async updateTitle(data) {
45240
+ data = data || this.data;
45241
+ const title = await this.computeTitle(data);
45242
+ this.titleSubject.next(title);
45243
+ // If NOT data, then add to page history
45244
+ if (!this.isNewData) {
45245
+ const page = await this.computePageHistory(title);
45246
+ if (page)
45247
+ return this.addToPageHistory(page);
45248
+ }
45249
+ }
45250
+ async addToPageHistory(page, opts) {
45251
+ if (!page)
45252
+ return; // Skip
45253
+ return this.settings.addToPageHistory(page, {
45254
+ removePathQueryParams: true,
45255
+ removeTitleSmallTag: true,
45256
+ emitEvent: false,
45257
+ ...opts,
44783
45258
  });
44784
45259
  }
44785
- cancel() {
44786
- this.viewCtrl.dismiss();
45260
+ async computePageHistory(title) {
45261
+ if (this.debug)
45262
+ console.debug('[entity-editor] Computing page history, using url: ' + this.router.url);
45263
+ return {
45264
+ title,
45265
+ path: this.router.url,
45266
+ };
44787
45267
  }
44788
- async doSubmit() {
44789
- if (this.form.disabled)
44790
- return;
44791
- if (!this.form.valid) {
44792
- await AppFormUtils.waitWhilePending(this.form);
44793
- if (this.form.invalid) {
44794
- AppFormUtils.logFormErrors(this.form.form, '[message-modal] ');
44795
- this.form.markAllAsTouched();
44796
- return;
44797
- }
44798
- }
44799
- this.markAsLoading();
44800
- try {
44801
- const data = this.form.value;
44802
- // Disable the form
44803
- this.form.disable();
44804
- const entity = Message.fromObject(data);
44805
- return this.viewCtrl.dismiss(entity);
44806
- }
44807
- catch (err) {
44808
- this.form.error = (err && err.message) || err;
44809
- this.markAsLoaded();
44810
- // Enable the form
44811
- this.form.enable();
44812
- // Reset form error on next changes
44813
- firstNotNilPromise(this.form.form.valueChanges).then(() => {
44814
- this.form.error = null;
44815
- this.markForCheck();
44816
- });
44817
- return;
45268
+ async removePageHistory(opts) {
45269
+ return this.settings.removePageHistory(this.router.url, opts);
45270
+ }
45271
+ computePageUrl(id) {
45272
+ const parentUrl = this.getParentPageUrl();
45273
+ return parentUrl && `${parentUrl}/${id}`;
45274
+ }
45275
+ getParentPageUrl(withQueryParams) {
45276
+ let parentUrl = this.defaultBackHref;
45277
+ // Remove query params
45278
+ if (withQueryParams !== true && parentUrl && parentUrl.indexOf('?') !== -1) {
45279
+ parentUrl = parentUrl.substring(0, parentUrl.indexOf('?'));
44818
45280
  }
45281
+ return parentUrl;
45282
+ }
45283
+ listenChanges(id, opts) {
45284
+ return this.dataService.listenChanges(this.data.id, {
45285
+ // Make sure to skip cache, because NOT the full graph will be fetched by subscription
45286
+ // When a changes occur, we call reload() to force refetching the full graph
45287
+ fetchPolicy: 'no-cache',
45288
+ ...opts,
45289
+ });
44819
45290
  }
44820
45291
  markForCheck() {
44821
45292
  this.cd.markForCheck();
44822
45293
  }
44823
- markAsLoading(opts) {
44824
- this.form.markAsLoading(opts);
45294
+ /**
45295
+ * Update the route, without reloading the component
45296
+ *
45297
+ * @param data
45298
+ * @param queryParams
45299
+ * @protected
45300
+ */
45301
+ async updateRoute(data, queryParams) {
45302
+ data = data || this.data;
45303
+ if (!data || !this.route)
45304
+ return false;
45305
+ const currId = this.route.snapshot.paramMap.get(this.pathIdAttribute);
45306
+ const futureId = isNotNil(data.id) ? data.id.toString() : 'new';
45307
+ if (queryParams) {
45308
+ this.queryParams = {
45309
+ ...this.queryParams,
45310
+ ...queryParams,
45311
+ };
45312
+ }
45313
+ if (currId === futureId) {
45314
+ // Update queryParam only (not the path)
45315
+ // /!\ We should get query Params from the updated route, and not from snapshot that can be outdated
45316
+ const actualQueryParams = await firstNotNilPromise(this.route.queryParams);
45317
+ if (!equals(actualQueryParams, this.queryParams)) {
45318
+ if (this.debug)
45319
+ console.debug(`${this._logPrefix}Updating route using queryParams: `, this.queryParams);
45320
+ return await this.router.navigate(['.'], {
45321
+ relativeTo: this.route,
45322
+ replaceUrl: false,
45323
+ queryParams: this.queryParams,
45324
+ state: { animated: false },
45325
+ });
45326
+ }
45327
+ }
45328
+ else {
45329
+ const path = this.computePageUrl(isNotNil(data.id) ? data.id : 'new');
45330
+ const commands = path && typeof path === 'string' ? path.split('/').slice(1) : path;
45331
+ if (isNotEmptyArray(commands)) {
45332
+ // Current route was '/new' => change to '/new?id=:id' (to allow CustomReuseStrategy to detect that route can be reused)
45333
+ if (currId === 'new' && this.queryParams[this.pathIdAttribute] !== futureId) {
45334
+ if (this.debug)
45335
+ console.debug(`${this._logPrefix}Updating route /new into /:id, using queryParams: `, queryParams);
45336
+ this.queryParams[this.pathIdAttribute] = futureId;
45337
+ const res = await this.router.navigate(['.'], {
45338
+ relativeTo: this.route,
45339
+ replaceUrl: true,
45340
+ queryParams: this.queryParams,
45341
+ state: { animated: false },
45342
+ });
45343
+ if (!res)
45344
+ return false;
45345
+ }
45346
+ if (this.debug)
45347
+ console.debug(`${this._logPrefix}Updating route to: ` + path);
45348
+ return await this.router.navigate(commands, {
45349
+ replaceUrl: true,
45350
+ queryParams: this.queryParams,
45351
+ state: { animated: false },
45352
+ });
45353
+ }
45354
+ return Promise.reject('Missing page URL');
45355
+ }
44825
45356
  }
44826
- markAsLoaded(opts) {
44827
- this.form.markAsLoaded(opts);
45357
+ /* -- private functions -- */
45358
+ /**
45359
+ * Open the first tab that is invalid
45360
+ */
45361
+ openFirstInvalidTab() {
45362
+ const invalidTabIndex = this.getFirstInvalidTabIndex();
45363
+ if (invalidTabIndex !== -1 && this.selectedTabIndex !== invalidTabIndex) {
45364
+ this.selectedTabIndex = invalidTabIndex;
45365
+ this.markForCheck();
45366
+ }
44828
45367
  }
44829
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModal, deps: [{ token: LocalSettingsService }, { token: i2$1.ModalController }, { token: AccountService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
44830
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: MessageModal, selector: "app-message-modal", inputs: { suggestFn: "suggestFn", data: "data", canSelectType: "canSelectType", canRecipientFilter: "canRecipientFilter", recipientFilterCount: "recipientFilterCount" }, viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, static: true }], ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ 'SOCIAL.MESSAGE.NEW.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-item *ngIf=\"form.error\" visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"form.error | translate\"></ion-label>\n </ion-item>\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-label translate>COMMON.BTN_SEND</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n", dependencies: [{ kind: "directive", type: i3$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2$1.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonFooter, selector: "ion-footer", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i2$1.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i2$1.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "directive", type: i1$1.TranslateDirective, selector: "[translate],[ngx-translate]", inputs: ["translate", "translateParams"] }, { kind: "component", type: MessageForm, selector: "app-message-form", inputs: ["suggestFn", "subjectMinLength", "subjectMaxLength", "bodyMaxLength", "bodyAutoHeight", "canSelectType", "canRecipientFilter", "recipientFilterCount"] }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
45368
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppEntityEditor, deps: "invalid", target: i0.ɵɵFactoryTarget.Directive });
45369
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: AppEntityEditor, usesInheritance: true, ngImport: i0 });
44831
45370
  }
44832
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModal, decorators: [{
44833
- type: Component,
44834
- args: [{ selector: 'app-message-modal', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"secondary\">\n <ion-buttons slot=\"start\">\n <ion-button class=\"back-button\" (click)=\"cancel()\" visible-xs visible-sm visible-mobile>\n <ion-icon slot=\"icon-only\" name=\"arrow-back\"></ion-icon>\n </ion-button>\n </ion-buttons>\n\n <ion-title>\n {{ 'SOCIAL.MESSAGE.NEW.TITLE' | translate }}\n </ion-title>\n\n <ion-buttons slot=\"end\">\n <ion-button\n class=\"back-button\"\n (click)=\"doSubmit()\"\n [disabled]=\"!form.valid\"\n visible-xs\n visible-sm\n visible-mobile\n >\n <ion-icon slot=\"icon-only\" name=\"checkmark\"></ion-icon>\n </ion-button>\n </ion-buttons>\n </ion-toolbar>\n</ion-header>\n\n<ion-content class=\"ion-padding\">\n <ion-item *ngIf=\"form.error\" visible-xs visible-sm visible-mobile lines=\"none\">\n <ion-icon color=\"danger\" slot=\"start\" name=\"alert-circle\"></ion-icon>\n <ion-label color=\"danger\" class=\"error\" [innerHTML]=\"form.error | translate\"></ion-label>\n </ion-item>\n\n <app-message-form\n #form\n (onSubmit)=\"doSubmit()\"\n (onCancel)=\"cancel()\"\n [canSelectType]=\"canSelectType\"\n [canRecipientFilter]=\"canRecipientFilter\"\n [recipientFilterCount]=\"recipientFilterCount\"\n [suggestFn]=\"suggestFn\"\n ></app-message-form>\n</ion-content>\n\n<ion-footer hidden-xs hidden-sm hidden-mobile>\n <ion-toolbar>\n <ion-row class=\"ion-no-padding\" nowrap>\n <ion-col></ion-col>\n\n <!-- buttons -->\n <ion-col size=\"auto\">\n <ion-button fill=\"clear\" color=\"dark\" (click)=\"cancel()\">\n <ion-label translate>COMMON.BTN_CANCEL</ion-label>\n </ion-button>\n\n <ion-button\n [fill]=\"form.invalid ? 'clear' : 'solid'\"\n [disabled]=\"form.loading || form.invalid\"\n (keyup.enter)=\"doSubmit()\"\n (click)=\"doSubmit()\"\n color=\"tertiary\"\n >\n <ion-label translate>COMMON.BTN_SEND</ion-label>\n </ion-button>\n </ion-col>\n </ion-row>\n </ion-toolbar>\n</ion-footer>\n" }]
44835
- }], ctorParameters: () => [{ type: LocalSettingsService }, { type: i2$1.ModalController }, { type: AccountService }, { type: i0.ChangeDetectorRef }], propDecorators: { suggestFn: [{
44836
- type: Input
44837
- }], data: [{
44838
- type: Input
44839
- }], canSelectType: [{
44840
- type: Input
44841
- }], canRecipientFilter: [{
44842
- type: Input
44843
- }], recipientFilterCount: [{
44844
- type: Input
44845
- }], form: [{
44846
- type: ViewChild,
44847
- args: ['form', { static: true }]
44848
- }] } });
45371
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: AppEntityEditor, decorators: [{
45372
+ type: Directive
45373
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: undefined }, { type: undefined }, { type: AppEditorOptions, decorators: [{
45374
+ type: Optional
45375
+ }] }] });
44849
45376
 
44850
45377
  class MessageModule {
44851
45378
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
44852
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: MessageModule, declarations: [MessageModal, MessageForm], imports: [CommonModule, CoreModule, SharedModule, MatChipsModule], exports: [MessageModal] });
44853
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModule, imports: [CommonModule, CoreModule, SharedModule, MatChipsModule] });
45379
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.13", ngImport: i0, type: MessageModule, declarations: [MessageModal, MessageForm], imports: [CommonModule, CoreModule, SharedModule, MatChipsModule, SharedDebugModule], exports: [MessageModal] });
45380
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModule, imports: [CommonModule, CoreModule, SharedModule, MatChipsModule, SharedDebugModule] });
44854
45381
  }
44855
45382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageModule, decorators: [{
44856
45383
  type: NgModule,
44857
45384
  args: [{
44858
- imports: [CommonModule, CoreModule, SharedModule, MatChipsModule],
45385
+ imports: [CommonModule, CoreModule, SharedModule, MatChipsModule, SharedDebugModule],
44859
45386
  declarations: [MessageModal, MessageForm],
44860
45387
  exports: [MessageModal],
44861
45388
  }]
@@ -45283,271 +45810,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
45283
45810
  }]
45284
45811
  }] });
45285
45812
 
45286
- const Mutations = {
45287
- send: gql$1 `
45288
- mutation SendMessage($data: MessageVOInput) {
45289
- done: sendMessage(message: $data)
45290
- }
45291
- `,
45292
- };
45293
- class MessageService extends BaseGraphqlService {
45294
- graphql;
45295
- translate;
45296
- modalCtrl;
45297
- toastController;
45298
- environment;
45299
- constructor(graphql, translate, modalCtrl, toastController, environment) {
45300
- super(graphql, environment);
45301
- this.graphql = graphql;
45302
- this.translate = translate;
45303
- this.modalCtrl = modalCtrl;
45304
- this.toastController = toastController;
45305
- this.environment = environment;
45306
- // For DEV only
45307
- this._debug = !environment.production;
45308
- }
45309
- /**
45310
- * Send a message to recipient(s)
45311
- *
45312
- * @param entity
45313
- * @param opts
45314
- */
45315
- async send(entity, opts) {
45316
- // Transform into json
45317
- const data = entity.asObject(MINIFY_ENTITY_FOR_POD);
45318
- const now = Date.now();
45319
- if (this._debug)
45320
- console.debug(`[message-service] Sending message...`);
45321
- try {
45322
- const { done } = await this.graphql.mutate({
45323
- mutation: Mutations.send,
45324
- variables: { data },
45325
- error: { code: SocialErrorCodes.SEND_MESSAGE_ERROR, message: 'SOCIAL.ERROR.SEND_MESSAGE_ERROR' },
45326
- });
45327
- if (this._debug)
45328
- console.debug(`[message-service] Send message [OK] in ${Date.now() - now}ms`);
45329
- if (done && (!opts || opts.showToast !== false)) {
45330
- await this.showToast({ type: 'info', message: 'SOCIAL.INFO.MESSAGE_SENT' });
45331
- }
45332
- return done;
45333
- }
45334
- catch (err) {
45335
- const error = (err && err.message) || err;
45336
- console.error(error);
45337
- // Show error
45338
- if (!opts || opts.showToast !== false) {
45339
- const message = error || 'SOCIAL.ERROR.SEND_MESSAGE_ERROR';
45340
- await this.showToast({ type: 'error', message });
45341
- }
45342
- return false;
45343
- }
45344
- }
45345
- async openComposeModal(options) {
45346
- const hasTopModal = !!(await this.modalCtrl.getTop());
45347
- const modal = await this.modalCtrl.create({
45348
- component: MessageModal,
45349
- componentProps: options,
45350
- cssClass: hasTopModal && 'stack-modal',
45351
- });
45352
- // Open the modal
45353
- await modal.present();
45354
- // On dismiss
45355
- const { data } = await modal.onDidDismiss();
45356
- if (!data || !(data instanceof Message))
45357
- return true; // CANCELLED
45358
- // Send message
45359
- return await this.send(data, { showToast: options?.showToast });
45360
- }
45361
- /* -- protected methods -- */
45362
- async showToast(opts) {
45363
- return Toasts.show(this.toastController, this.translate, {
45364
- type: 'info',
45365
- message: 'SOCIAL.INFO.MESSAGE_SENT',
45366
- ...opts,
45367
- });
45368
- }
45369
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageService, deps: [{ token: GraphqlService }, { token: i1$1.TranslateService }, { token: i2$1.ModalController }, { token: i2$1.ToastController }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
45370
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageService, providedIn: 'root' });
45371
- }
45372
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: MessageService, decorators: [{
45373
- type: Injectable,
45374
- args: [{ providedIn: 'root' }]
45375
- }], ctorParameters: () => [{ type: GraphqlService }, { type: i1$1.TranslateService }, { type: i2$1.ModalController }, { type: i2$1.ToastController }, { type: Environment, decorators: [{
45376
- type: Optional
45377
- }, {
45378
- type: Inject,
45379
- args: [ENVIRONMENT]
45380
- }] }] });
45381
-
45382
- const PersonFragments = {
45383
- person: gql$1 `
45384
- fragment PersonFragment on PersonVO {
45385
- id
45386
- firstName
45387
- lastName
45388
- email
45389
- pubkey
45390
- avatar
45391
- statusId
45392
- updateDate
45393
- creationDate
45394
- profiles
45395
- username
45396
- usernameExtranet
45397
- department {
45398
- id
45399
- label
45400
- name
45401
- logo
45402
- __typename
45403
- }
45404
- __typename
45405
- }
45406
- `,
45407
- };
45408
- // Load persons query
45409
- const PersonQueries = {
45410
- loadAll: gql$1 `
45411
- query Persons($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: PersonFilterVOInput) {
45412
- data: persons(filter: $filter, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection) {
45413
- ...PersonFragment
45414
- }
45415
- }
45416
- ${PersonFragments.person}
45417
- `,
45418
- loadAllWithTotal: gql$1 `
45419
- query PersonsWithTotal($offset: Int, $size: Int, $sortBy: String, $sortDirection: String, $filter: PersonFilterVOInput) {
45420
- data: persons(filter: $filter, offset: $offset, size: $size, sortBy: $sortBy, sortDirection: $sortDirection) {
45421
- ...PersonFragment
45422
- }
45423
- total: personsCount(filter: $filter)
45424
- }
45425
- ${PersonFragments.person}
45426
- `,
45427
- };
45428
- const PersonMutations = {
45429
- saveAll: gql$1 `
45430
- mutation savePersons($data: [PersonVOInput]) {
45431
- data: savePersons(persons: $data) {
45432
- ...PersonFragment
45433
- }
45434
- }
45435
- ${PersonFragments.person}
45436
- `,
45437
- deleteAll: gql$1 `
45438
- mutation deletePersons($ids: [Int]) {
45439
- deletePersons(ids: $ids)
45440
- }
45441
- `,
45442
- };
45443
- class PersonService extends BaseEntityService {
45444
- graphql;
45445
- platform;
45446
- network;
45447
- entities;
45448
- constructor(graphql, platform, network, entities) {
45449
- super(graphql, platform, Person, PersonFilter, {
45450
- queries: PersonQueries,
45451
- mutations: PersonMutations,
45452
- defaultSortBy: 'lastName',
45453
- defaultSortDirection: 'asc',
45454
- });
45455
- this.graphql = graphql;
45456
- this.platform = platform;
45457
- this.network = network;
45458
- this.entities = entities;
45459
- // for DEV only -----
45460
- this._debug = !environment.production;
45461
- }
45462
- async loadAll(offset, size, sortBy, sortDirection, filter, opts) {
45463
- const offline = this.network.offline && (!opts || opts.fetchPolicy !== 'network-only');
45464
- if (offline) {
45465
- return this.loadAllLocally(offset, size, sortBy, sortDirection, filter, opts);
45466
- }
45467
- return super.loadAll(offset, size, sortBy, sortDirection, filter, opts);
45468
- }
45469
- async loadAllLocally(offset, size, sortBy, sortDirection, filter, opts) {
45470
- filter = this.asFilter(filter);
45471
- const variables = {
45472
- offset: offset || 0,
45473
- size: size || 100,
45474
- sortBy: sortBy || this.defaultSortBy,
45475
- sortDirection: sortDirection || this.defaultSortDirection,
45476
- filter: filter && filter.asFilterFn(),
45477
- };
45478
- const { data, total } = await this.entities.loadAll('PersonVO', variables);
45479
- const entities = this.fromObjects(data, opts);
45480
- const res = { data: entities, total };
45481
- // Add fetch more function
45482
- const nextOffset = (offset || 0) + entities.length;
45483
- if (nextOffset < total) {
45484
- res.fetchMore = () => this.loadAllLocally(nextOffset, size, sortBy, sortDirection, filter, opts);
45485
- }
45486
- return res;
45487
- }
45488
- async suggest(value, filter, sortBy, sortDirection, opts) {
45489
- if (EntityUtils.isNotEmpty(value, 'id'))
45490
- return { data: [value] };
45491
- value = (typeof value === 'string' && value !== '*' && value) || undefined;
45492
- sortBy = sortBy || filter.searchAttribute || (filter.searchAttributes && filter.searchAttributes[0]);
45493
- return this.loadAll(0, !value ? 30 : 10, sortBy, sortDirection, {
45494
- ...filter,
45495
- searchText: value,
45496
- statusIds: (filter && filter.statusIds) || [StatusIds.ENABLE, StatusIds.TEMPORARY],
45497
- userProfiles: filter && filter.userProfiles,
45498
- }, {
45499
- withTotal: true /* need by autocomplete */,
45500
- ...opts,
45501
- });
45502
- }
45503
- async executeImport(filter, opts) {
45504
- const maxProgression = (opts && opts.maxProgression) || 100;
45505
- filter = {
45506
- ...filter,
45507
- statusIds: [StatusIds.ENABLE, StatusIds.TEMPORARY],
45508
- userProfiles: ['SUPERVISOR', 'USER', 'GUEST'],
45509
- };
45510
- console.info('[person-service] Importing persons...');
45511
- const res = await JobUtils.fetchAllPages((offset, size) => this.loadAll(offset, size, 'id', null, filter, {
45512
- debug: false,
45513
- fetchPolicy: 'network-only',
45514
- withTotal: offset === 0, // Compute total only once
45515
- toEntity: false,
45516
- }), { progression: opts?.progression, maxProgression: maxProgression * 0.9 });
45517
- // Save result locally
45518
- await this.entities.saveAll(res.data, { entityName: 'PersonVO', reset: true });
45519
- }
45520
- async loadById(id, opts) {
45521
- const { data } = await this.loadAll(0, 1, null, null, { includedIds: [id] }, { withTotal: false, ...opts });
45522
- const source = isNotEmptyArray(data) ? data[0] : { id };
45523
- return this.fromObject(source, opts);
45524
- }
45525
- async loadByPubkey(pubkey, opts) {
45526
- const { data } = await this.loadAll(0, 1, null, null, { pubkey }, { withTotal: false, ...opts });
45527
- const source = isNotEmptyArray(data) ? data[0] : { pubkey };
45528
- return this.fromObject(source, opts);
45529
- }
45530
- /* -- protected methods -- */
45531
- asObject(source) {
45532
- if (!source)
45533
- return undefined;
45534
- if (!(source instanceof Person)) {
45535
- source = Person.fromObject(source);
45536
- }
45537
- const target = source.asObject();
45538
- // Not known in server GraphQL schema
45539
- delete target.mainProfile;
45540
- target.department = source.department?.asObject();
45541
- return target;
45542
- }
45543
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PersonService, deps: [{ token: GraphqlService }, { token: PlatformService }, { token: NetworkService }, { token: EntitiesStorage }], target: i0.ɵɵFactoryTarget.Injectable });
45544
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PersonService, providedIn: 'root' });
45545
- }
45546
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: PersonService, decorators: [{
45547
- type: Injectable,
45548
- args: [{ providedIn: 'root' }]
45549
- }], ctorParameters: () => [{ type: GraphqlService }, { type: PlatformService }, { type: NetworkService }, { type: EntitiesStorage }] });
45550
-
45551
45813
  class UsersPage extends AppTable {
45552
45814
  accountService;
45553
45815
  validatorService;
@@ -50149,11 +50411,11 @@ class FeedTestingPage {
50149
50411
  this.cd.markForCheck();
50150
50412
  }
50151
50413
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedTestingPage, deps: [{ token: LocalSettingsService }, { token: i0.ChangeDetectorRef }, { token: ENVIRONMENT, optional: true }], target: i0.ɵɵFactoryTarget.Component });
50152
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FeedTestingPage, selector: "app-feed-test", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Feed test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <ion-grid class=\"form-container ion-padding-top\">\n <ion-row>\n <ion-col size=\"12\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Feed URL(s)</mat-label>\n <mat-select [(value)]=\"feedUrls\" multiple>\n <mat-option *ngFor=\"let feedUrl of availableFeedUrls\" [value]=\"feedUrl\">\n {{ feedUrl }}\n </mat-option>\n <mat-option>(Use environment)</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Header color</mat-label>\n <mat-select [(value)]=\"headerColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Card color</mat-label>\n <mat-select [(value)]=\"cardColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Shape</mat-label>\n <mat-select [(value)]=\"shape\">\n <mat-option *ngFor=\"let shape of availableShapes\" [value]=\"shape\">\n {{ shape }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"debug = $event.checked\" [checked]=\"debug\">Debug ?</mat-checkbox>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <div class=\"ion-padding\">\n <app-feed\n [feedUrls]=\"feedUrls\"\n [headerColor]=\"headerColor\"\n [cardColor]=\"cardColor\"\n [shape]=\"shape\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n</ion-content>\n", dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "feedUrls", "maxAgeInMonths", "maxContentLength"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
50414
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FeedTestingPage, selector: "app-feed-test", ngImport: i0, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Feed test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <ion-grid class=\"form-container ion-padding-top\">\n <ion-row>\n <ion-col size=\"12\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Feed URL(s)</mat-label>\n <mat-select [(value)]=\"feedUrls\" multiple>\n <mat-option *ngFor=\"let feedUrl of availableFeedUrls\" [value]=\"feedUrl\">\n {{ feedUrl }}\n </mat-option>\n <mat-option>(Use environment)</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Header color</mat-label>\n <mat-select [(value)]=\"headerColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Card color</mat-label>\n <mat-select [(value)]=\"cardColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Shape</mat-label>\n <mat-select [(value)]=\"shape\">\n <mat-option *ngFor=\"let shape of availableShapes\" [value]=\"shape\">\n {{ shape }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"debug = $event.checked\" [checked]=\"debug\">Debug ?</mat-checkbox>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"feedUrls\"\n [headerColor]=\"headerColor\"\n [cardColor]=\"cardColor\"\n [shape]=\"shape\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n</ion-content>\n", dependencies: [{ kind: "directive", type: i3$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: i2$1.IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: i2$1.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i2$1.IonContent, selector: "ion-content", inputs: ["color", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: i2$1.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i2$1.IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: i2$1.IonRow, selector: "ion-row" }, { kind: "component", type: i2$1.IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: i2$1.IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: i2$1.IonBackButton, selector: "ion-back-button" }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: FeedsComponent, selector: "app-feed", inputs: ["debug", "mobile", "showHeader", "showReadMoreButton", "headerColor", "cardColor", "shape", "class", "itemId", "filterItem", "feeds", "urls", "maxAgeInMonths", "maxContentLength"], outputs: ["editItem", "deleteItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
50153
50415
  }
50154
50416
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FeedTestingPage, decorators: [{
50155
50417
  type: Component,
50156
- args: [{ selector: 'app-feed-test', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Feed test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <ion-grid class=\"form-container ion-padding-top\">\n <ion-row>\n <ion-col size=\"12\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Feed URL(s)</mat-label>\n <mat-select [(value)]=\"feedUrls\" multiple>\n <mat-option *ngFor=\"let feedUrl of availableFeedUrls\" [value]=\"feedUrl\">\n {{ feedUrl }}\n </mat-option>\n <mat-option>(Use environment)</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Header color</mat-label>\n <mat-select [(value)]=\"headerColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Card color</mat-label>\n <mat-select [(value)]=\"cardColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Shape</mat-label>\n <mat-select [(value)]=\"shape\">\n <mat-option *ngFor=\"let shape of availableShapes\" [value]=\"shape\">\n {{ shape }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"debug = $event.checked\" [checked]=\"debug\">Debug ?</mat-checkbox>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <div class=\"ion-padding\">\n <app-feed\n [feedUrls]=\"feedUrls\"\n [headerColor]=\"headerColor\"\n [cardColor]=\"cardColor\"\n [shape]=\"shape\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n</ion-content>\n" }]
50418
+ args: [{ selector: 'app-feed-test', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ion-header>\n <ion-toolbar color=\"primary\">\n <ion-buttons slot=\"start\">\n <ion-back-button></ion-back-button>\n </ion-buttons>\n\n <ion-title>Feed test page</ion-title>\n </ion-toolbar>\n</ion-header>\n\n<ion-content>\n <ion-grid class=\"form-container ion-padding-top\">\n <ion-row>\n <ion-col size=\"12\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Feed URL(s)</mat-label>\n <mat-select [(value)]=\"feedUrls\" multiple>\n <mat-option *ngFor=\"let feedUrl of availableFeedUrls\" [value]=\"feedUrl\">\n {{ feedUrl }}\n </mat-option>\n <mat-option>(Use environment)</mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n </ion-row>\n <ion-row>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Header color</mat-label>\n <mat-select [(value)]=\"headerColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Card color</mat-label>\n <mat-select [(value)]=\"cardColor\">\n <mat-option *ngFor=\"let color of availableColors\" [value]=\"color\">\n {{ color }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-form-field [subscriptSizing]=\"'dynamic'\">\n <mat-label>Shape</mat-label>\n <mat-select [(value)]=\"shape\">\n <mat-option *ngFor=\"let shape of availableShapes\" [value]=\"shape\">\n {{ shape }}\n </mat-option>\n </mat-select>\n </mat-form-field>\n </ion-col>\n <ion-col size=\"3\">\n <mat-checkbox (change)=\"debug = $event.checked\" [checked]=\"debug\">Debug ?</mat-checkbox>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <div class=\"ion-padding\">\n <app-feed\n [urls]=\"feedUrls\"\n [headerColor]=\"headerColor\"\n [cardColor]=\"cardColor\"\n [shape]=\"shape\"\n [debug]=\"debug\"\n ></app-feed>\n </div>\n</ion-content>\n" }]
50157
50419
  }], ctorParameters: () => [{ type: LocalSettingsService }, { type: i0.ChangeDetectorRef }, { type: Environment, decorators: [{
50158
50420
  type: Optional
50159
50421
  }, {
@@ -50268,5 +50530,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImpo
50268
50530
  * Generated bundle index. Do not edit.
50269
50531
  */
50270
50532
 
50271
- export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOTKEYS_CONFIG, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CapitalizePipe, CellValueChangeListener, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileResponse, FileService, FileSizePipe, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NOOP_ITEM_FILTER, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NavActionsColumnComponent, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedTextFormModule, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, compareVersionNumbers, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hexToRgb, hexToRgbArray, initArrayControlsFromValues, interpolateString, intersectArrays, isAndroid, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isMacOS, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isTouchUi, isVersionCompatible, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, truncateHtml, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
50533
+ export { APP_ABOUT_DEVELOPERS, APP_ABOUT_PARTNERS, APP_CONFIG_OPTIONS, APP_DEBUG_DATA_SERVICE, APP_FEED_SERVICE, APP_FORM_ERROR_I18N_KEYS, APP_GRAPHQL_FRAGMENTS, APP_GRAPHQL_TYPE_POLICIES, APP_HOME_BUTTONS, APP_HOME_CONFIG, APP_HOTKEYS_CONFIG, APP_JOB_PROGRESSION_SERVICE, APP_LOCALES, APP_LOCAL_SETTINGS, APP_LOCAL_SETTINGS_OPTIONS, APP_LOCAL_STORAGE_TYPE_POLICIES, APP_LOGGING_SERVICE, APP_MENU_ITEMS, APP_MENU_OPTIONS, APP_NAMED_FILTER_SERVICE, APP_PROGRESS_BAR_SERVICE, APP_SETTINGS_MENU_ITEMS, APP_STORAGE, APP_STORAGE_EXPLORER_PROTECTED_KEYS, APP_TESTING_PAGES, APP_USER_EVENT_LIST_INFINITE_SCROLL_THRESHOLD, APP_USER_EVENT_SERVICE, APP_USER_SETTINGS_OPTIONS, APP_USER_TOKEN_SCOPES, AboutModal, AbstractNamedFilterService, AbstractSelectionModelPipe, AbstractTableSelectionPipe, AbstractUserEventService, Account, AccountPage, AccountService, AccountToStringPipe, AccountUtils, ActionsColumnComponent, AdminModule, AdminRoutingModule, AdminUsersModule, Alerts, AndroidOsEnvironment, AppAboutModalModule, AppAccountModule, AppAsyncTable, AppAuthForm, AppAuthModal, AppAuthModule, AppChangePasswordModule, AppChangePasswordPage, AppEditor, AppEditorOptions, AppEntityEditor, AppEntityEditorModal, AppEntityEditorModalOptions, AppEntityFormModule, AppForm, AppFormArray, AppFormButtonsBarModule, AppFormContainer, AppFormField, AppFormModule, AppFormProvider, AppFormUtils, AppGestureConfig, AppGraphQLModule, AppHomePageModule, AppIconComponent, AppIconModule, AppImageGalleryComponent, AppInMemoryTable, AppInstallUpgradeCard, AppInstallUpgradeCardModule, AppListForm, AppListFormModule, AppLoadingSpinner, AppMarkdownContent, AppMarkdownModal, AppMenuModule, AppNullForm, AppPropertiesForm, AppPropertiesFormModule, AppPropertiesTable, AppPropertiesUtils, AppPropertyUtils, AppRegisterModule, AppResetPasswordModal, AppRowField, AppSelectPeerModule, AppSettingsPageModule, AppTabEditor, AppTabEditorOptions, AppTable, AppTableModule, AppTableUtils, AppTextPopoverModule, AppUpdateOfflineModeCard, AppUpdateOfflineModeCardModule, AppValidatorService, AppendQueryParamsPipePipe, ArrayDistinctPipe, ArrayFilterPipe, ArrayFindByPropertyPipe, ArrayFirstPipe, ArrayFormTestPage, ArrayIncludesPipe, ArrayJoinPipe, ArrayLastPipe, ArrayLengthPipe, ArrayMapPipe, ArrayPluckPipe, ArraySortPipe, AsAnyPipe, AsArrayPipe, AsBooleanPipe, AsFloatLabelTypePipe, AsObservablePipe, AudioProvider, AudioTestingModule, AudioTestingPage, AuthGuardService, AutoResizeDirective, AutoTitleDirective, AutocompleteTestPage, AutofocusDirective, BadgeDirective, BadgeNumberPipe, Base58, BaseEntityService, BaseGraphqlService, BaseGraphqlServiceOptions, BaseReferential, Beans, BooleanFormatPipe, BooleanTestPage, CORE_CONFIG_OPTIONS, CORE_TESTING_PAGES, CapitalizePipe, CellValueChangeListener, ChangePasswordForm, ChipsTestPage, Color, ColorScale, ComponentDirtyGuard, ConfigFragments, ConfigService, Configuration, CoreModule, CorePipesModule, CoreTestingModule, CryptoService, CsvUtils, DATE_ISO_PATTERN, DATE_MATCH_REGEXP, DATE_PATTERN, DATE_UNIX_MS_TIMESTAMP, DATE_UNIX_TIMESTAMP, DEFAULT_JOIN_ARRAY_VALUES_SEPARATOR, DEFAULT_JOIN_PROPERTIES_SEPARATOR, DEFAULT_MENU_SHOW_WHEN, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLACEHOLDER_CHAR, DEFAULT_REQUIRED_COLUMNS, DateDiffDurationPipe, DateFormatPipe, DateFormatService, DateFromNowPipe, DateShortTestPage, DateTestPage, DateTimeTestPage, DateUtils, DebugComponent, Department, DepartmentToStringPipe, DisplayWithPipe, DragAndDropDirective, DurationPipe, DurationTestPage, ED25519_SEED_LENGTH, EMPTY_PLACEHOLDER_CHAR, EMPTY_PLACEHOLDER_CHAR_REGEXP_GLOBAL, ENTITIES_STORAGE_KEY_PREFIX, ENVIRONMENT, EmptyArrayPipe, EntitiesAsyncTableDataSource, EntitiesStorage, EntitiesTableDataSource, Entity, EntityClass, EntityClasses, EntityFilter, EntityFilterUtils, EntityMetadataComponent, EntityStore, EntityUtils, Environment, EnvironmentHttpLoader, EnvironmentLoader, ErrorCodes, EvenPipe, FeedDirective, FeedModule, FeedPage, FeedService, FeedsComponent, FileResponse, FileService, FileSizePipe, FilesUtils, FirstFalsePipe, FirstPipe, FirstTruePipe, FormArrayHelper, FormArrayTestModule, FormButtonsBarComponent, FormButtonsBarToken, FormErrorPipe, FormErrorTranslatePipe, FormErrorTranslator, FormFieldDefinitionUtils, FormFieldValuesHolder, FormGetArrayPipe, FormGetControlPipe, FormGetGroupPipe, FormGetPipe, FormGetValuePipe, GalleryTestPage, GeolocationUtils, GraphqlService, HAMMER_PRESS_TIME, HAMMER_TAP_TIME, HighlightPipe, HomePage, Hotkeys, HotkeysDialogComponent, IMAGE_DEFAULTS, IPosition, ImageAttachment, ImageAttachmentFilter, ImageAttachmentService, ImageGalleryModule, ImageGalleryTestingModule, ImageModule, ImageService, ImagesUtils, InMemoryEntitiesService, IsAllSelectedPipe, IsEmptySelectionPipe, IsLoginAccountPipe, IsMultipleSelectionPipe, IsNilOrBlankPipe, IsNilOrNaNPipe, IsNilPipe, IsNotAllSelectedPipe, IsNotEmptySelectionPipe, IsNotNilOrBlankPipe, IsNotNilOrNaNPipe, IsNotNilPipe, IsOnDeskPipe, IsOnFieldPipe, IsSelectedPipe, IsSingleSelectionPipe, IsValidDatePipe, JobModule, JobProgression, JobProgressionComponent, JobProgressionIcon, JobProgressionList, JobProgressionService, JobProgressionTestService, JobProgressionTestingPage, JobTestingModule, JobUtils, JsonFeedUtils, JsonUtils, KEYBOARD_HIDE_DELAY_MS, LAT_LONG_PATTERNS, LAT_LONG_PATTERN_MAX_DECIMALS, LAT_LONG_VALUE_MAX_DECIMALS, LatLongFormatPipe, LatLongTestPage, LatitudeFormatPipe, LocalSettingsService, LogLevel, LogUtils, Logger, LoggingService, LoggingServiceModule, LongitudeFormatPipe, MASKS, MASK_RANGES, MAT_FORM_FIELD_DEFAULT_APPEARANCE, MAT_FORM_FIELD_DEFAULT_SUBSCRIPT_SIZING, MINIFY_ENTITY_FOR_LOCAL_STORAGE, MINIFY_ENTITY_FOR_POD, MOMENT_NO_TIME_PROPERTY, MapGetPipe, MapKeysPipe, MapPipe, MapToPipe, MapValuesPipe, MarkdownDirective, MarkdownService, MarkdownTestPage, MarkdownTestingModule, MarkdownUtils, MaskitoPlaceholderPipe, MaskitoTestPage, MatAutocompleteConfigHolder, MatAutocompleteField, MatAutocompleteFieldUtils, MatBadgeTestPage, MatBooleanField, MatChipsField, MatColorPipe, MatCommonTestPage, MatDate, MatDateShort, MatDateTime, MatDuration, MatLatLongField, MatLatLongFieldInput, MatPaginatorI18n, MatStepperI18n, MatSwipeField, MaterialTestingModule, MathAbsPipe, MenuComponent, MenuItem, MenuItems, MenuOptions, MenuService, MenuTestingModule, MenuTestingPage, Message, MessageFilter, MessageForm, MessageModal, MessageModule, MessageService, MessageTypeList, MessageTypes, MimeTypes, ModalToolbarComponent, NETWORK_DEFAULT_CONNECTION_TIMEOUT, NamedFilter, NamedFilterFilter, NamedFilterSelector, NamedFilterSelectorTestingModule, NamedFilterSelectorTestingPage, NavActionsColumnComponent, NetworkService, NetworkUtils, NewTokenForm, NewTokenModal, NgInitDirective, NgVarDirective, NoHtmlPipe, NotEmptyArrayPipe, NumberFormatPipe, ObservableTestPage, OddPipe, OtherMenuTestingPage, PEER_URL_REGEXP, PLUS_PLACEHOLDER_CHAR_REGEXP_GLOBAL, PRINT_ID_QUERY_PARAM, PRINT_LOADING_STORAGE_KEY_PREFIX, PRIORITIZED_AUTHORITIES, PUBKEY_REGEXP, Peer, Person, PersonFilter, PersonFragments, PersonService, PersonToStringPipe, PersonUtils, PersonValidatorService, PlatformService, PrintService, ProgressBarService, ProgressInterceptor, PropertiesFormTestPage, PropertiesFormTestingModule, PropertyEntity, PropertyEntityFilter, PropertyEntityValidator, PropertyFormatPipe, PropertyGetPipe, RESERVED_END_COLUMNS, RESERVED_START_COLUMNS, Referential, ReferentialFilter, ReferentialRef, ReferentialToStringPipe, ReferentialUtils, ReferentialValidatorService, RegExpUtils, RegisterConfirmPage, RegisterForm, RegisterModal, ResizableComponent, ResizableDirective, ResizableModule, RoundPipe, RxStateComputed, RxStateModule, RxStateProperty, RxStateRegister, RxStateSelect, SCRYPT_PARAMS, SETTINGS_COMPACT_ROWS, SETTINGS_DISPLAY_COLUMNS, SETTINGS_FILTER, SETTINGS_PAGE_SIZE, SETTINGS_SORTED_COLUMN, SETTINGS_STORAGE_KEY, SETTINGS_TRANSIENT_PROPERTIES, SHARED_MATERIAL_TESTING_PAGES, SHARED_STORAGE_TESTING_PAGES, SHARED_TESTING_PAGES, SOCIAL_CONFIG_OPTIONS, SOCIAL_TESTING_PAGES, SPACE_PLACEHOLDER_CHAR, SPACE_PLACEHOLDER_CHAR_REGEXP_GLOBAL, SafeHtmlPipe, SafeStylePipe, SelectPeerModal, SelectionLengthPipe, ServerErrorCodes, SettingsPage, SharedAsyncValidators, SharedBadgeModule, SharedDebugModule, SharedDirectivesModule, SharedFormArrayValidators, SharedFormGroupValidators, SharedHotkeysModule, SharedMarkdownModule, SharedMatAutocompleteModule, SharedMatBooleanModule, SharedMatChipsModule, SharedMatDateTimeModule, SharedMatDurationModule, SharedMatLatLongModule, SharedMatSwipeModule, SharedMaterialModule, SharedModule, SharedNamedFilterModule, SharedPipesModule, SharedRoutingModule, SharedTestingModule, SharedTestsPage, SharedTextFormModule, SharedToolbarModule, SharedValidators, SocialErrorCodes, SocialModule, SocialModuleOptionsToken, SocialTestingModule, Software, SplitArrayInChunksPipe, StartableService, StatusById, StatusIds, StatusList, StorageDrivers, StorageExplorerComponent, StorageExplorerModule, StorageExplorerTestingModule, StorageExplorerTestingRoutingModule, StorageService, StrIncludesPipe, StrLengthPipe, StrReplacePipe, SubMenuTabDirective, SwipeTestPage, TABLE_SETTINGS_ENUM, TOOLBAR_HEADER_ID, Table2TestPage, TableSelectColumnsComponent, TableTestPage, TableTestingModule, TableValidatorService, TextForm, TextFormTestingPage, TextPopover, TextPopoverTestingModule, TextPopoverTestingPage, ThrottledClickDirective, ToStringPipe, ToastTestingModule, ToastTestingPage, Toasts, TokenScope, ToolbarComponent, ToolbarToken, TranslatablePipe, TranslateContextPipe, TranslateContextService, TreeItemEntityUtils, TruncHtmlPipe, TruncTextPipe, TruncateHtmlPipe, UploadFile, UploadFileComponent, UploadFilePopover, UploadFileTestingModule, UploadFileTestingPage, UriUtils, UrlUtils, UserEventModule, UserEventNotificationIcon, UserEventNotificationList, UserEventNotificationModal, UserEventTestService, UserEventTestingModule, UserEventTestingPage, UserSettings, UserToken, UserTokenTable, UsersPage, ValueFormatPipe, VersionUtils, accountToString, adaptValueToControl, addValueInArray, arrayDistinct, arrayResize, arraySize, asInputElement, base64ArrayBuffer, booleanToString, canHaveFocus, capitalizeFirstLetter, chainPromises, changeCaseToUnderscore, clearValueInArray, collectByProperty, compareVersionNumbers, computeDecimalDegrees, computeDecimalPart, copyEntity2Form, createPromiseEvent, createPromiseEventEmitter, departmentToString, departmentsToString, disableAndClearControl, disableAndClearControls, disableControl, disableControls, emitPromiseEvent, enableControl, enableControls, enableRxStateProdMode, entityToString, equals, equalsOrNil, escapeRegExp, expansionAnimation, fadeInAnimation, fadeInOutAnimation, fadeInSlowAnimation, filterFalse, filterFormErrors, filterFormErrorsByPath, filterFormErrorsByPrefix, filterNotNil, filterNumberInput, filterTrue, findParentWithClass, firstArrayValue, firstFalse, firstFalsePromise, firstNotNil, firstNotNilPromise, firstTrue, firstTruePromise, focusInput, focusNextInput, focusPreviousInput, formatLatLong, formatLatitude, formatLongitude, fromDateISOString, fromScrollEndEvent, fromUnixMsTimestamp, fromUnixTimestamp, getCaretPosition, getColorContrast, getColorShade, getColorTint, getControlFromPath, getFocusableInputElements, getFormErrors, getFormValueFromEntity, getInputRangeFromCaretIndex, getInputSelectionRangesFromMask, getProperty, getPropertyByPath, getPropertyByPathAsString, getRandomImage, getRandomImageWithCredit, getUserAgent, hexToRgb, hexToRgbArray, initArrayControlsFromValues, interpolateString, intersectArrays, isAndroid, isBlankString, isCapacitor, isChrome, isControlHasInput, isEdge, isEmptyArray, isEntityService, isFirefox, isIOS, isInputElement, isInstanceOf, isInt, isIpad, isMacOS, isMobile, isNil, isNilOrBlank, isNilOrNaN, isNotEmptyArray, isNotNil, isNotNilBoolean, isNotNilObject, isNotNilOrBlank, isNotNilOrNaN, isNotNilString, isNumber, isNumberRange, isOnFieldMode, isPrint, isProgressEvent, isPromise, isResponseEvent, isSafari, isSameVersion, isStartableService, isTouchUi, isVersionCompatible, isWindows, joinProperties, joinPropertiesPath, lastArrayValue, logFormErrors, markAllAsTouched, markAsUntouched, markControlAsTouched, markFormGroupAsTouched, maskitoAutoSelectByMaskPattern, maskitoPrefixPlugin, matchMedia, matchUpperCase, mergeLoadResult, mixHex, moveInputCaretToSeparator, newArray, noHtml, noTrailingSlash, notNilOrDefault, nullIfNilOrBlank, nullIfUndefined, numberOrNilAttribute, numberToString, parseLatitudeOrLongitude, propertiesPathComparator, propertyComparator, propertyPathComparator, referentialToString, referentialsToString, remove, removeAll, removeDiacritics, removeDuplicatesFromArray, removeEnd, removeValueInArray, replaceAll, resetCalculatedValue, resizeArray, rgbArrayToHex, rgbToHex, round, scrollFactory, selectInputContent, selectInputContentFromEvent, selectInputRange, setCalculatedValue, setControlEnabled, setControlsEnabled, setFormErrors, setPropertyByPath, setTabIndex, sleep, slideDownAnimation, slideInAnimation, slideInOutAnimation, slideUpDownAnimation, sort, splitArrayInChunks, splitById, splitByProperty, splitDegreesToDDArray, splitDegreesToDDMMArray, splitDegreesToDDMMSSArray, startsWithUpperCase, suggestFromArray, suggestFromStringArray, tabindexComparator, testUserAgent, toBoolean, toDateISOString, toDuration, toFloat, toInt, toNotNil, toNumber, trimEmptyToNull, truncateHtml, uncapitalizeFirstLetter, undefinedIfNull, underscoreToChangeCase, updateValueAndValidity, waitFor, waitForFalse, waitForTrue, waitIdle, waitWhilePending };
50272
50534
  //# sourceMappingURL=sumaris-net.ngx-components.mjs.map