@ecodev/natural 55.1.0 → 55.2.0

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.
@@ -1,6 +1,6 @@
1
1
  import '@angular/localize/init';
2
2
  import * as i0 from '@angular/core';
3
- import { Directive, Component, Inject, Injectable, HostBinding, HostListener, inject, InjectionToken, TemplateRef, ViewEncapsulation, ViewChild, Injector, Optional, Input, Host, Self, EventEmitter, Output, Pipe, APP_INITIALIZER, ContentChild, createEnvironmentInjector, createComponent, runInInjectionContext, PLATFORM_ID, ErrorHandler, importProvidersFrom } from '@angular/core';
3
+ import { Directive, Component, Inject, Injectable, HostBinding, HostListener, inject, InjectionToken, TemplateRef, ViewEncapsulation, ViewChild, Injector, Optional, Input, Host, Self, EventEmitter, Output, Pipe, LOCALE_ID, APP_INITIALIZER, ContentChild, createEnvironmentInjector, createComponent, runInInjectionContext, PLATFORM_ID, ErrorHandler, importProvidersFrom } from '@angular/core';
4
4
  import { Subject, BehaviorSubject, of, timer, switchMap as switchMap$1, endWith, last, EMPTY, merge as merge$1, Observable, first as first$1, combineLatest, catchError, from, ReplaySubject, debounceTime as debounceTime$1, raceWith, take as take$1, mergeMap, shareReplay as shareReplay$1, forkJoin, map as map$1, tap as tap$1, asyncScheduler } from 'rxjs';
5
5
  import * as i2$3 from '@angular/forms';
6
6
  import { FormGroup, FormArray, Validators, UntypedFormGroup, UntypedFormArray, FormControl, FormsModule, ReactiveFormsModule, UntypedFormControl, FormControlDirective, FormControlName } from '@angular/forms';
@@ -391,6 +391,68 @@ function deepFreeze(o) {
391
391
  Object.values(o).forEach(v => Object.isFrozen(v) || deepFreeze(v));
392
392
  return Object.freeze(o);
393
393
  }
394
+ /**
395
+ * Return a valid PaginationInput from whatever is available from data. Invalid properties/types will be dropped.
396
+ */
397
+ function validatePagination(data) {
398
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
399
+ return null;
400
+ }
401
+ const pagination = {};
402
+ if ('offset' in data && (data.offset === null || typeof data.offset === 'number')) {
403
+ pagination.offset = data.offset;
404
+ }
405
+ if ('pageIndex' in data && (data.pageIndex === null || typeof data.pageIndex === 'number')) {
406
+ pagination.pageIndex = data.pageIndex;
407
+ }
408
+ if ('pageSize' in data && (data.pageSize === null || typeof data.pageSize === 'number')) {
409
+ pagination.pageSize = data.pageSize;
410
+ }
411
+ return pagination;
412
+ }
413
+ /**
414
+ * Return a valid Sortings from whatever is available from data. Invalid properties/types will be dropped.
415
+ */
416
+ function validateSorting(data) {
417
+ if (!Array.isArray(data)) {
418
+ return null;
419
+ }
420
+ const result = [];
421
+ data.forEach(s => {
422
+ const r = validateOneSorting(s);
423
+ if (r) {
424
+ result.push(r);
425
+ }
426
+ });
427
+ return result;
428
+ }
429
+ function validateOneSorting(data) {
430
+ if (!data || typeof data !== 'object' || !('field' in data)) {
431
+ return null;
432
+ }
433
+ const sorting = { field: data.field };
434
+ if ('order' in data &&
435
+ (data.order === SortingOrder.ASC || data.order === SortingOrder.DESC || data.order === null)) {
436
+ sorting.order = data.order;
437
+ }
438
+ if ('nullAsHighest' in data && (data.nullAsHighest === null || typeof data.nullAsHighest === 'boolean')) {
439
+ sorting.nullAsHighest = data.nullAsHighest;
440
+ }
441
+ if ('emptyStringAsHighest' in data &&
442
+ (data.emptyStringAsHighest === null || typeof data.emptyStringAsHighest === 'boolean')) {
443
+ sorting.emptyStringAsHighest = data.emptyStringAsHighest;
444
+ }
445
+ return sorting;
446
+ }
447
+ /**
448
+ * Return valid columns from whatever is available from data. Invalid properties/types will be dropped.
449
+ */
450
+ function validateColumns(data) {
451
+ if (typeof data !== 'string') {
452
+ return null;
453
+ }
454
+ return data.split(',').filter(string => string);
455
+ }
394
456
 
395
457
  // Basic; loosely typed structure for graphql-doctrine filters
396
458
  // Logical operator to be used in conditions
@@ -3872,19 +3934,19 @@ class NaturalAbstractList extends NaturalAbstractPanel {
3872
3934
  }
3873
3935
  const storageKey = this.getStorageKey();
3874
3936
  // Pagination : pa
3875
- const pagination = this.persistenceService.get('pa', this.route, storageKey);
3937
+ const pagination = validatePagination(this.persistenceService.get('pa', this.route, storageKey));
3876
3938
  if (pagination) {
3877
3939
  this.variablesManager.set('pagination', { pagination });
3878
3940
  }
3879
3941
  // Sorting : so
3880
- const sorting = this.persistenceService.get('so', this.route, storageKey);
3942
+ const sorting = validateSorting(this.persistenceService.get('so', this.route, storageKey));
3881
3943
  if (sorting) {
3882
3944
  this.variablesManager.set('sorting', { sorting });
3883
3945
  }
3884
3946
  // Columns
3885
- const persistedColumns = this.persistenceService.get('col', this.route, storageKey);
3886
- if (typeof persistedColumns === 'string') {
3887
- this.selectedColumns = persistedColumns.split(',');
3947
+ const persistedColumns = validateColumns(this.persistenceService.get('col', this.route, storageKey));
3948
+ if (persistedColumns) {
3949
+ this.selectedColumns = persistedColumns;
3888
3950
  }
3889
3951
  // Natural search : ns
3890
3952
  this.naturalSearchSelections = fromUrl(this.persistenceService.get('ns', this.route, storageKey));
@@ -5305,7 +5367,7 @@ function getTabId(tab) {
5305
5367
  /**
5306
5368
  * Usage :
5307
5369
  *
5308
- * <mat-tab-group mat-stretch-tabs="false" [naturalLinkableTab]="!isPanel">
5370
+ * <mat-tab-group [naturalLinkableTab]="!isPanel">
5309
5371
  * <mat-tab label="Third 1">third 1</mat-tab> // First tab doesn't need id. This keeps url clean on default one
5310
5372
  * <mat-tab label="Third 2" id="third2">Third 2</mat-tab>
5311
5373
  * ...
@@ -5730,11 +5792,13 @@ function stripTags(str) {
5730
5792
  * configured for it in the routing.
5731
5793
  */
5732
5794
  class NaturalSeoService {
5733
- constructor(config, router, titleService, metaTagService) {
5795
+ constructor(config, router, titleService, metaTagService, document, locale) {
5734
5796
  this.config = config;
5735
5797
  this.router = router;
5736
5798
  this.titleService = titleService;
5737
5799
  this.metaTagService = metaTagService;
5800
+ this.document = document;
5801
+ this.locale = locale;
5738
5802
  this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
5739
5803
  const root = this.router.routerState.root.snapshot;
5740
5804
  this.routeData = this.getRouteData(root);
@@ -5776,6 +5840,65 @@ class NaturalSeoService {
5776
5840
  // Robots
5777
5841
  const robots = seo?.robots ?? this.config.defaultRobots;
5778
5842
  this.updateTag('robots', robots);
5843
+ // Canonical
5844
+ // Add language in url (after domain) if some languages are provided only
5845
+ const language = this.config.languages?.length && this.locale ? this.locale.split('-')[0] : '';
5846
+ const urlParts = this.getUrlParts(seo.canonicalQueryParamsWhitelist || []);
5847
+ this.updateLinkTag({ rel: 'canonical', href: this.getUrl(urlParts, language) });
5848
+ this.updateAlternates(urlParts);
5849
+ }
5850
+ updateAlternates(urlParts) {
5851
+ this.config.languages?.forEach(language => {
5852
+ this.updateLinkTag({ rel: 'alternate', href: this.getUrl(urlParts, language), hreflang: language });
5853
+ });
5854
+ }
5855
+ getUrlParts(whiteListedParams) {
5856
+ let url = 'https://' + this.document.defaultView?.window.location.hostname;
5857
+ const urlTree = this.router.parseUrl(this.router.url);
5858
+ // need better like something recursive ?
5859
+ if (urlTree.root.hasChildren()) {
5860
+ const segments = urlTree.root.children['primary'].segments;
5861
+ if (segments && segments.length > 0) {
5862
+ url += '/' + segments.map(segment => segment.path).join('/');
5863
+ }
5864
+ }
5865
+ // Query Params
5866
+ let params = '';
5867
+ for (const param in urlTree.queryParams) {
5868
+ if (whiteListedParams.includes(param)) {
5869
+ const key = encodeURIComponent(param);
5870
+ const value = encodeURIComponent(urlTree.queryParams[param]);
5871
+ if (params.length) {
5872
+ params += '&';
5873
+ }
5874
+ params += `${key}=${value}`;
5875
+ }
5876
+ }
5877
+ if (params.length) {
5878
+ params = '?' + params;
5879
+ }
5880
+ return { url, params };
5881
+ }
5882
+ /**
5883
+ * Add language between domain and uri https://example.com/fr/folder/page
5884
+ * @param urlParts
5885
+ * @param language
5886
+ * @private
5887
+ */
5888
+ getUrl(urlParts, language) {
5889
+ let url = urlParts.url;
5890
+ if (language) {
5891
+ url = this.addLanguageSegment(url, language);
5892
+ }
5893
+ if (urlParts.params) {
5894
+ url += urlParts.params;
5895
+ }
5896
+ return url;
5897
+ }
5898
+ addLanguageSegment(url, language) {
5899
+ const urlObj = new URL(url);
5900
+ const newPath = urlObj.pathname === '/' ? `/${language}` : `/${language}${urlObj.pathname}`;
5901
+ return urlObj.origin + newPath + urlObj.search + urlObj.hash;
5779
5902
  }
5780
5903
  join(parts) {
5781
5904
  return parts.filter(s => !!s).join(' - ');
@@ -5791,6 +5914,29 @@ class NaturalSeoService {
5791
5914
  this.metaTagService.removeTag(`name="${name}"`);
5792
5915
  }
5793
5916
  }
5917
+ updateLinkTag(definition) {
5918
+ const linkElement = this.document.head.querySelector(this._parseSelector(definition)) ||
5919
+ this.document.head.appendChild(this.document.createElement('link'));
5920
+ if (linkElement) {
5921
+ Object.keys(definition).forEach((attribute) => {
5922
+ linkElement.setAttribute(attribute, definition[attribute]);
5923
+ });
5924
+ }
5925
+ }
5926
+ /**
5927
+ * Parse tag to create a selector
5928
+ * @param definition
5929
+ * @return {string} selector to use in querySelector
5930
+ */
5931
+ _parseSelector(definition) {
5932
+ let attributes = 'link';
5933
+ Object.keys(definition).forEach((attr) => {
5934
+ if (attr !== 'href') {
5935
+ attributes += `[${attr}="${definition[attr]}"]`;
5936
+ }
5937
+ });
5938
+ return attributes;
5939
+ }
5794
5940
  /**
5795
5941
  * Returns the data from the most deep/specific activated route
5796
5942
  */
@@ -5834,7 +5980,7 @@ class NaturalSeoService {
5834
5980
  }
5835
5981
  return seo;
5836
5982
  }
5837
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NaturalSeoService, deps: [{ token: NATURAL_SEO_CONFIG }, { token: i2$4.Router }, { token: i2$5.Title }, { token: i2$5.Meta }], target: i0.ɵɵFactoryTarget.Injectable }); }
5983
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NaturalSeoService, deps: [{ token: NATURAL_SEO_CONFIG }, { token: i2$4.Router }, { token: i2$5.Title }, { token: i2$5.Meta }, { token: DOCUMENT }, { token: LOCALE_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
5838
5984
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NaturalSeoService, providedIn: 'root' }); }
5839
5985
  }
5840
5986
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NaturalSeoService, decorators: [{
@@ -5845,7 +5991,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImpor
5845
5991
  }], ctorParameters: function () { return [{ type: undefined, decorators: [{
5846
5992
  type: Inject,
5847
5993
  args: [NATURAL_SEO_CONFIG]
5848
- }] }, { type: i2$4.Router }, { type: i2$5.Title }, { type: i2$5.Meta }]; } });
5994
+ }] }, { type: i2$4.Router }, { type: i2$5.Title }, { type: i2$5.Meta }, { type: Document, decorators: [{
5995
+ type: Inject,
5996
+ args: [DOCUMENT]
5997
+ }] }, { type: undefined, decorators: [{
5998
+ type: Inject,
5999
+ args: [LOCALE_ID]
6000
+ }] }]; } });
5849
6001
 
5850
6002
  /**
5851
6003
  * Configure and starts `NaturalSeoService`
@@ -11208,5 +11360,5 @@ function graphqlQuerySigner(key) {
11208
11360
  * Generated bundle index. Do not edit.
11209
11361
  */
11210
11362
 
11211
- export { AvatarService, LOCAL_STORAGE, NATURAL_DROPDOWN_DATA, NATURAL_ICONS_CONFIG, NATURAL_PERSISTENCE_VALIDATOR, NATURAL_SEO_CONFIG, NaturalAbstractController, NaturalAbstractDetail, NaturalAbstractEditableList, NaturalAbstractList, NaturalAbstractModelService, NaturalAbstractNavigableList, NaturalAbstractPanel, NaturalAlertService, NaturalAvatarComponent, NaturalCapitalizePipe, NaturalColumnsPickerComponent, NaturalConfirmComponent, NaturalDataSource, NaturalDebounceService, NaturalDetailHeaderComponent, NaturalDialogTriggerComponent, NaturalDropdownRef, NaturalEllipsisPipe, NaturalEnumPipe, NaturalEnumService, NaturalErrorHandler, NaturalFileComponent, NaturalFileDropDirective, NaturalFileSelectDirective, NaturalFileService, NaturalFixedButtonComponent, NaturalFixedButtonDetailComponent, NaturalHierarchicSelectorComponent, NaturalHierarchicSelectorDialogComponent, NaturalHierarchicSelectorDialogService, NaturalHierarchicSelectorService, NaturalHttpPrefixDirective, NaturalIconDirective, NaturalLinkMutationService, NaturalLinkableTabDirective, NaturalLoggerConfigExtra, NaturalLoggerConfigUrl, NaturalMatomoService, NaturalMemoryStorage, NaturalPanelsComponent, NaturalPanelsService, NaturalPersistenceService, NaturalQueryVariablesManager, NaturalRelationsComponent, NaturalSearchComponent, NaturalSelectComponent, NaturalSelectEnumComponent, NaturalSelectHierarchicComponent, NaturalSeoService, NaturalSidenavComponent, NaturalSidenavContainerComponent, NaturalSidenavContentComponent, NaturalSidenavService, NaturalSidenavStackService, NaturalSrcDensityDirective, NaturalStampComponent, NaturalSwissDatePipe, NaturalSwissParsingDateAdapter, NaturalTableButtonComponent, NaturalTimeAgoPipe, PanelsHooksConfig, SESSION_STORAGE, SortingOrder, TypeBooleanComponent, TypeDateComponent, TypeDateRangeComponent, TypeHierarchicSelectorComponent, TypeNaturalSelectComponent, TypeNumberComponent, TypeOptionsComponent, TypeSelectComponent, TypeTextComponent, available, cancellableTimeout, cleanSameValues, collectErrors, copyToClipboard, createHttpLink, debug, decimal, deepFreeze, deliverableEmail, ensureHttpPrefix, fallbackIfNoOpenedPanels, formatIsoDate, formatIsoDateTime, fromUrl, getForegroundColor, graphqlQuerySigner, ifValid, integer, localStorageFactory, localStorageProvider, lowerCaseFirstLetter, makePlural, memoryLocalStorageProvider, memorySessionStorageProvider, mergeOverrideArray, money, naturalPanelsUrlMatcher, naturalProviders, provideErrorHandler, provideIcons, providePanels, provideSeo, relationsToIds, replaceObjectKeepingReference, replaceOperatorByField, replaceOperatorByName, sessionStorageFactory, sessionStorageProvider, toGraphQLDoctrineFilter, toNavigationParameters, toUrl, unique, upperCaseFirstLetter, urlValidator, validTlds, validateAllFormControls, wrapLike };
11363
+ export { AvatarService, LOCAL_STORAGE, NATURAL_DROPDOWN_DATA, NATURAL_ICONS_CONFIG, NATURAL_PERSISTENCE_VALIDATOR, NATURAL_SEO_CONFIG, NaturalAbstractController, NaturalAbstractDetail, NaturalAbstractEditableList, NaturalAbstractList, NaturalAbstractModelService, NaturalAbstractNavigableList, NaturalAbstractPanel, NaturalAlertService, NaturalAvatarComponent, NaturalCapitalizePipe, NaturalColumnsPickerComponent, NaturalConfirmComponent, NaturalDataSource, NaturalDebounceService, NaturalDetailHeaderComponent, NaturalDialogTriggerComponent, NaturalDropdownRef, NaturalEllipsisPipe, NaturalEnumPipe, NaturalEnumService, NaturalErrorHandler, NaturalFileComponent, NaturalFileDropDirective, NaturalFileSelectDirective, NaturalFileService, NaturalFixedButtonComponent, NaturalFixedButtonDetailComponent, NaturalHierarchicSelectorComponent, NaturalHierarchicSelectorDialogComponent, NaturalHierarchicSelectorDialogService, NaturalHierarchicSelectorService, NaturalHttpPrefixDirective, NaturalIconDirective, NaturalLinkMutationService, NaturalLinkableTabDirective, NaturalLoggerConfigExtra, NaturalLoggerConfigUrl, NaturalMatomoService, NaturalMemoryStorage, NaturalPanelsComponent, NaturalPanelsService, NaturalPersistenceService, NaturalQueryVariablesManager, NaturalRelationsComponent, NaturalSearchComponent, NaturalSelectComponent, NaturalSelectEnumComponent, NaturalSelectHierarchicComponent, NaturalSeoService, NaturalSidenavComponent, NaturalSidenavContainerComponent, NaturalSidenavContentComponent, NaturalSidenavService, NaturalSidenavStackService, NaturalSrcDensityDirective, NaturalStampComponent, NaturalSwissDatePipe, NaturalSwissParsingDateAdapter, NaturalTableButtonComponent, NaturalTimeAgoPipe, PanelsHooksConfig, SESSION_STORAGE, SortingOrder, TypeBooleanComponent, TypeDateComponent, TypeDateRangeComponent, TypeHierarchicSelectorComponent, TypeNaturalSelectComponent, TypeNumberComponent, TypeOptionsComponent, TypeSelectComponent, TypeTextComponent, available, cancellableTimeout, cleanSameValues, collectErrors, copyToClipboard, createHttpLink, debug, decimal, deepFreeze, deliverableEmail, ensureHttpPrefix, fallbackIfNoOpenedPanels, formatIsoDate, formatIsoDateTime, fromUrl, getForegroundColor, graphqlQuerySigner, ifValid, integer, localStorageFactory, localStorageProvider, lowerCaseFirstLetter, makePlural, memoryLocalStorageProvider, memorySessionStorageProvider, mergeOverrideArray, money, naturalPanelsUrlMatcher, naturalProviders, provideErrorHandler, provideIcons, providePanels, provideSeo, relationsToIds, replaceObjectKeepingReference, replaceOperatorByField, replaceOperatorByName, sessionStorageFactory, sessionStorageProvider, toGraphQLDoctrineFilter, toNavigationParameters, toUrl, unique, upperCaseFirstLetter, urlValidator, validTlds, validateAllFormControls, validateColumns, validatePagination, validateSorting, wrapLike };
11212
11364
  //# sourceMappingURL=ecodev-natural.mjs.map