@lenne.tech/ng-base 0.3.2 → 0.3.6

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 (32) hide show
  1. package/bundles/lenne.tech-ng-base-src-lib-base.umd.js +781 -749
  2. package/bundles/lenne.tech-ng-base-src-lib-base.umd.js.map +1 -1
  3. package/bundles/lenne.tech-ng-base.umd.js +781 -749
  4. package/bundles/lenne.tech-ng-base.umd.js.map +1 -1
  5. package/esm2015/lib/base/classes/helper.class.js +19 -1
  6. package/esm2015/lib/base/directives/match-height.directive.js +19 -14
  7. package/esm2015/lib/base/factories/apollo-options.factory.js +3 -2
  8. package/esm2015/lib/base/services/image.service.js +24 -38
  9. package/esm2015/lib/base/services/scroll.service.js +8 -29
  10. package/esm2015/lib/base/services/seo.service.js +70 -14
  11. package/esm2015/src/lib/base/classes/helper.class.js +19 -1
  12. package/esm2015/src/lib/base/directives/match-height.directive.js +19 -14
  13. package/esm2015/src/lib/base/factories/apollo-options.factory.js +3 -2
  14. package/esm2015/src/lib/base/services/image.service.js +24 -38
  15. package/esm2015/src/lib/base/services/scroll.service.js +8 -29
  16. package/esm2015/src/lib/base/services/seo.service.js +70 -14
  17. package/fesm2015/lenne.tech-ng-base-src-lib-base.js +517 -473
  18. package/fesm2015/lenne.tech-ng-base-src-lib-base.js.map +1 -1
  19. package/fesm2015/lenne.tech-ng-base.js +517 -473
  20. package/fesm2015/lenne.tech-ng-base.js.map +1 -1
  21. package/lib/base/classes/helper.class.d.ts +7 -0
  22. package/lib/base/directives/match-height.directive.d.ts +7 -7
  23. package/lib/base/services/image.service.d.ts +1 -2
  24. package/lib/base/services/scroll.service.d.ts +1 -2
  25. package/lib/base/services/seo.service.d.ts +36 -3
  26. package/package.json +1 -1
  27. package/src/lib/base/classes/helper.class.d.ts +7 -0
  28. package/src/lib/base/directives/match-height.directive.d.ts +7 -7
  29. package/src/lib/base/lenne.tech-ng-base-src-lib-base.metadata.json +1 -1
  30. package/src/lib/base/services/image.service.d.ts +1 -2
  31. package/src/lib/base/services/scroll.service.d.ts +1 -2
  32. package/src/lib/base/services/seo.service.d.ts +36 -3
@@ -2,13 +2,14 @@ import * as i1 from 'apollo-angular';
2
2
  import { APOLLO_OPTIONS, gql, Apollo } from 'apollo-angular';
3
3
  import { HttpLink } from 'apollo-angular/http';
4
4
  import * as i0 from '@angular/core';
5
- import { InjectionToken, Injectable, Inject, Directive, ElementRef, Input, Output, HostBinding, NgZone, HostListener, Pipe, NgModule } from '@angular/core';
5
+ import { InjectionToken, Injectable, Inject, Directive, ElementRef, Input, Output, HostBinding, PLATFORM_ID, HostListener, Pipe, NgModule } from '@angular/core';
6
6
  import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
7
7
  import { ApolloLink, split, concat } from '@apollo/client/core';
8
8
  import { InMemoryCache } from '@apollo/client/cache';
9
9
  import { WebSocketLink } from '@apollo/client/link/ws';
10
10
  import { getMainDefinition } from '@apollo/client/utilities';
11
11
  import { __awaiter } from 'tslib';
12
+ import { isPlatformBrowser } from '@angular/common';
12
13
  import * as i1$2 from '@angular/platform-browser';
13
14
  import { DomSanitizer, Title, Meta } from '@angular/platform-browser';
14
15
  import * as i1$1 from '@angular/router';
@@ -212,8 +213,9 @@ AuthService.ctorParameters = () => [
212
213
  */
213
214
  function apolloOptionsFactory(baseModuleConfig, httpLink, authService) {
214
215
  const links = [];
216
+ const defaultUrl = 'api.' + window.location.href + '/graphql';
215
217
  const http = httpLink.create({
216
- uri: baseModuleConfig.apiUrl,
218
+ uri: baseModuleConfig.apiUrl ? baseModuleConfig.apiUrl : 'https://' + defaultUrl,
217
219
  });
218
220
  const authMiddleware = new ApolloLink((operation, forward) => {
219
221
  const headers = {};
@@ -418,362 +420,6 @@ LazyLoadDirective.propDecorators = {
418
420
  src: [{ type: Input }]
419
421
  };
420
422
 
421
- /**
422
- * Image service
423
- */
424
- class ImageService {
425
- /**
426
- * Preaload images (for lazy handling)
427
- *
428
- * @param options
429
- * @protected
430
- */
431
- preLoadImages(options) {
432
- // Init configuration
433
- const config = Object.assign({ doneFunnction: undefined, elements: document, regExp: undefined, timeout: undefined }, options);
434
- return new Promise((resolve) => {
435
- var _a;
436
- // Init variables
437
- let resolved = false;
438
- let timeoutId;
439
- // HTMLCollection to array
440
- if (config.elements instanceof HTMLCollection) {
441
- config.elements = Array.from(config.elements);
442
- }
443
- // Single element to array
444
- if (!Array.isArray(config.elements)) {
445
- config.elements = [config.elements];
446
- }
447
- // Init loading
448
- const promises = [];
449
- // Get all images
450
- let images = [];
451
- config.elements.forEach((element) => {
452
- images = images.concat(Array.from(element.getElementsByTagName('img')));
453
- });
454
- // Prepare images
455
- for (const image of images) {
456
- const src = image.getAttribute('src');
457
- // Check if changes are necessary and possible
458
- if ((image.complete && image.naturalHeight !== 0) || // already laoded
459
- image.getAttribute('loading') !== 'lazy' || // loading attribute !== lazy
460
- !src || // Image has no src
461
- ((_a = config.regExp) === null || _a === void 0 ? void 0 : _a.test(src)) // Test if src matches regular expression
462
- ) {
463
- continue;
464
- }
465
- // Replace lazy loading image with immediately loading image
466
- const newImage = document.createElement('img');
467
- Array.from(image.attributes).forEach((attr) => {
468
- if (attr.name !== 'loading' && attr.name !== 'src') {
469
- newImage.setAttribute(attr.name, attr.value);
470
- }
471
- });
472
- image.replaceWith(newImage);
473
- // Wait for laoding
474
- promises.push(this.loadImage(newImage, src));
475
- }
476
- if (config.timeout) {
477
- timeoutId = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
478
- resolved = true;
479
- if (config.doneFunnction) {
480
- yield config.doneFunction();
481
- }
482
- resolve(undefined);
483
- }));
484
- }
485
- // Wait until all images loaded
486
- Promise.all(promises)
487
- .catch((e) => console.error('Error during image loading', e))
488
- .finally(() => __awaiter(this, void 0, void 0, function* () {
489
- // Check if already resolved
490
- if (resolved) {
491
- return;
492
- }
493
- // Stop timeout
494
- if (timeoutId) {
495
- clearTimeout(timeoutId);
496
- }
497
- // Return result
498
- let result;
499
- if (config.doneFunnction) {
500
- result = yield config.doneFunction();
501
- }
502
- resolve(result);
503
- }));
504
- });
505
- }
506
- /**
507
- * Load image for image element
508
- *
509
- * @param imageElement
510
- * @param src
511
- * @protected
512
- */
513
- loadImage(imageElement, src) {
514
- return __awaiter(this, void 0, void 0, function* () {
515
- src = src || imageElement.src;
516
- return new Promise((resolve, reject) => {
517
- imageElement.onload = () => resolve(imageElement);
518
- imageElement.onerror = reject;
519
- imageElement.src = src;
520
- });
521
- });
522
- }
523
- }
524
- ImageService.ɵprov = i0.ɵɵdefineInjectable({ factory: function ImageService_Factory() { return new ImageService(); }, token: ImageService, providedIn: "root" });
525
- ImageService.decorators = [
526
- { type: Injectable, args: [{
527
- providedIn: 'root',
528
- },] }
529
- ];
530
-
531
- /**
532
- * Set same height (maximum) to different elements
533
- */
534
- class MatchHeightDirective {
535
- constructor(el, imageService, zone) {
536
- this.el = el;
537
- this.imageService = imageService;
538
- this.zone = zone;
539
- this.loadLazyImages = true;
540
- }
541
- onResize() {
542
- this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
543
- }
544
- ngAfterViewChecked() {
545
- this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
546
- }
547
- matchHeight(parent, target) {
548
- // Timeout is set to force Angular to wait for all actions and only continue once the process has run through.
549
- // Otherwise, an infinite loop will occur.
550
- setTimeout(() => __awaiter(this, void 0, void 0, function* () {
551
- if (!parent) {
552
- return;
553
- }
554
- let classNames;
555
- if (!Array.isArray(target)) {
556
- classNames = [target];
557
- }
558
- else {
559
- classNames = target;
560
- }
561
- for (const className of classNames) {
562
- // find all child structureElements with the selected class name
563
- const children = parent.getElementsByClassName(className);
564
- if (!children || children.length < 2) {
565
- continue;
566
- }
567
- // Load images of children
568
- if (this.loadLazyImages) {
569
- yield this.imageService.preLoadImages({ elements: children });
570
- }
571
- // reset all children height
572
- Array.from(children).forEach((element) => {
573
- element.style.height = 'initial';
574
- });
575
- // get all the child structureElements heights
576
- const itemHeights = Array.from(children).map((x) => x.getBoundingClientRect().height);
577
- // find out the tallest one
578
- const maxHeight = itemHeights.reduce((prev, curr) => (curr > prev ? curr : prev), 0);
579
- // update the child structureElements height to the tallest one
580
- Array.from(children).forEach((element) => (element.style.height = `${maxHeight}px`));
581
- }
582
- }), 1);
583
- }
584
- }
585
- MatchHeightDirective.decorators = [
586
- { type: Directive, args: [{
587
- selector: '[ltMatchHeight]',
588
- },] }
589
- ];
590
- MatchHeightDirective.ctorParameters = () => [
591
- { type: ElementRef },
592
- { type: ImageService },
593
- { type: NgZone }
594
- ];
595
- MatchHeightDirective.propDecorators = {
596
- ltMatchHeight: [{ type: Input }],
597
- loadLazyImages: [{ type: Input }],
598
- onResize: [{ type: HostListener, args: ['window:resize',] }]
599
- };
600
-
601
- /**
602
- * Reszise detection
603
- * (inpsired by: https://stackblitz.com/edit/angular-resizable)
604
- */
605
- class ResizableDirective {
606
- constructor(el) {
607
- this.el = el;
608
- this.resizableGrabWidth = 8;
609
- this.resizableMinWidth = 10;
610
- this.dragging = false;
611
- }
612
- ngOnInit() {
613
- this.addListenerToDocument();
614
- this.addListenerToElement();
615
- }
616
- mouseMoveG(evt) {
617
- if (!this.dragging) {
618
- return;
619
- }
620
- this.newWidth(evt.clientX - this.el.nativeElement.offsetLeft);
621
- evt.stopPropagation();
622
- }
623
- newWidth(wid) {
624
- const result = Math.max(this.resizableMinWidth, wid);
625
- this.el.nativeElement.style.width = result + 'px';
626
- }
627
- mouseDown(evt) {
628
- if (this.inDragRegion(evt)) {
629
- this.dragging = true;
630
- this.preventGlobalMouseEvents();
631
- evt.stopPropagation();
632
- }
633
- }
634
- restoreGlobalMouseEvents() {
635
- document.body.style['pointer-events'] = 'auto';
636
- }
637
- mouseUpG(evt) {
638
- if (!this.dragging) {
639
- return;
640
- }
641
- this.restoreGlobalMouseEvents();
642
- this.dragging = false;
643
- evt.stopPropagation();
644
- }
645
- mouseMove(evt) {
646
- if (this.inDragRegion(evt) || this.dragging) {
647
- this.el.nativeElement.style.cursor = 'col-resize';
648
- }
649
- else {
650
- this.el.nativeElement.style.cursor = 'default';
651
- }
652
- }
653
- preventGlobalMouseEvents() {
654
- document.body.style['pointer-events'] = 'none';
655
- }
656
- inDragRegion(evt) {
657
- return this.el.nativeElement.clientWidth - evt.clientX + this.el.nativeElement.offsetLeft < this.resizableGrabWidth;
658
- }
659
- addListenerToDocument() {
660
- document.addEventListener('mousemove', (e) => {
661
- this.mouseMoveG(e);
662
- }, true);
663
- document.addEventListener('mouseup', (e) => {
664
- this.mouseUpG(e);
665
- }, true);
666
- }
667
- addListenerToElement() {
668
- this.el.nativeElement.addEventListener('mousedown', (e) => {
669
- this.mouseDown(e);
670
- }, true);
671
- this.el.nativeElement.addEventListener('mousemove', (e) => {
672
- this.mouseMove(e);
673
- }, true);
674
- }
675
- }
676
- ResizableDirective.decorators = [
677
- { type: Directive, args: [{
678
- selector: '[ltResizable]',
679
- },] }
680
- ];
681
- ResizableDirective.ctorParameters = () => [
682
- { type: ElementRef }
683
- ];
684
- ResizableDirective.propDecorators = {
685
- resizableGrabWidth: [{ type: Input }],
686
- resizableMinWidth: [{ type: Input }]
687
- };
688
-
689
- /**
690
- * Truncate string with ellipses
691
- */
692
- class EllipsesPipe {
693
- transform(value, limit) {
694
- if (!value) {
695
- return null;
696
- }
697
- return value.length > limit ? value.substring(0, limit) + '...' : value;
698
- }
699
- }
700
- EllipsesPipe.decorators = [
701
- { type: Pipe, args: [{
702
- name: 'ltEllipses',
703
- },] }
704
- ];
705
-
706
- /**
707
- * Declarate html as safe
708
- */
709
- class SafeHtmlPipe {
710
- constructor(sanitized) {
711
- this.sanitized = sanitized;
712
- }
713
- transform(value) {
714
- return this.sanitized.bypassSecurityTrustHtml(value);
715
- }
716
- }
717
- SafeHtmlPipe.decorators = [
718
- { type: Pipe, args: [{ name: 'ltSafeHtml' },] }
719
- ];
720
- SafeHtmlPipe.ctorParameters = () => [
721
- { type: DomSanitizer }
722
- ];
723
-
724
- // Imported and exported elements
725
- const elements = [
726
- // Directives
727
- ContextMenuDirective,
728
- LazyLoadDirective,
729
- MatchHeightDirective,
730
- ResizableDirective,
731
- // Pipes
732
- EllipsesPipe,
733
- SafeHtmlPipe,
734
- ];
735
- /**
736
- * Base module
737
- */
738
- class BaseModule {
739
- /**
740
- * Set configuration of base module in root module of the app
741
- */
742
- static forRoot(baseModuleConfig = {}) {
743
- // Default config
744
- const config = Object.assign({ apiUrl: 'localhost:3000', authGuardRedirectUrl: '/auth', logging: false, version: null, prefix: null, scrollDetectionOffset: 200, scrollOffset: 100, storageType: 'local' }, baseModuleConfig);
745
- // Prepare providers
746
- const providers = [
747
- {
748
- provide: BASE_MODULE_CONFIG,
749
- useValue: baseModuleConfig,
750
- },
751
- ];
752
- // Add apollo features if API URL is set
753
- if (baseModuleConfig.apiUrl) {
754
- providers.push({
755
- provide: APOLLO_OPTIONS,
756
- useFactory: apolloOptionsFactory,
757
- deps: [BASE_MODULE_CONFIG, HttpLink, AuthService],
758
- });
759
- }
760
- else if (config.logging) {
761
- console.log('apiUrl is missing, ApolloLink is not initialized');
762
- }
763
- // Return root module
764
- return {
765
- ngModule: BaseModule,
766
- providers,
767
- };
768
- }
769
- }
770
- BaseModule.decorators = [
771
- { type: NgModule, args: [{
772
- declarations: elements,
773
- exports: elements,
774
- },] }
775
- ];
776
-
777
423
  /**
778
424
  * Helper class
779
425
  */
@@ -956,95 +602,458 @@ class Helper {
956
602
  }
957
603
  return dateString;
958
604
  }
959
- /**
960
- * Get browser name and version
961
- */
962
- static getBrowser() {
963
- const userAgent = navigator.userAgent;
964
- let data = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
965
- let version;
966
- if (/trident/i.test(data[1])) {
967
- version = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
968
- return { name: 'IE', version: version[1] || '' };
969
- }
970
- if (data[1] === 'Chrome') {
971
- version = userAgent.match(/\bOPR|Edge\/(\d+)/);
972
- if (version != null) {
973
- return { name: 'Opera', version: version[1] };
974
- }
605
+ /**
606
+ * Get browser name and version
607
+ */
608
+ static getBrowser() {
609
+ const userAgent = navigator.userAgent;
610
+ let data = userAgent.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
611
+ let version;
612
+ if (/trident/i.test(data[1])) {
613
+ version = /\brv[ :]+(\d+)/g.exec(userAgent) || [];
614
+ return { name: 'IE', version: version[1] || '' };
615
+ }
616
+ if (data[1] === 'Chrome') {
617
+ version = userAgent.match(/\bOPR|Edge\/(\d+)/);
618
+ if (version != null) {
619
+ return { name: 'Opera', version: version[1] };
620
+ }
621
+ }
622
+ data = data[2] ? [data[1], data[2]] : [navigator.appName, navigator.appVersion, '-?'];
623
+ version = userAgent.match(/version\/(\d+)/i);
624
+ if (version !== null) {
625
+ data.splice(1, 1, version[1]);
626
+ }
627
+ return {
628
+ name: data[0],
629
+ version: data[1],
630
+ };
631
+ }
632
+ /**
633
+ * Whether first parameter is a deep subset of second parameter
634
+ * Hint: functions will be ignored
635
+ */
636
+ static isSubset(a, b) {
637
+ // Equal
638
+ if (a === b) {
639
+ return true;
640
+ }
641
+ // Different type
642
+ if (typeof a !== typeof b) {
643
+ return false;
644
+ }
645
+ // Process array
646
+ if (Array.isArray(a)) {
647
+ for (const [key, value] of Object.entries(a)) {
648
+ if (Helper.isSubset(value, b[key])) {
649
+ return false;
650
+ }
651
+ }
652
+ return true;
653
+ }
654
+ // Process object
655
+ if (typeof a === 'object') {
656
+ const plain = JSON.parse(JSON.stringify(a));
657
+ for (const [key, value] of Object.entries(plain)) {
658
+ if (Helper.isSubset(value, b[key])) {
659
+ return false;
660
+ }
661
+ }
662
+ return true;
663
+ }
664
+ // Else
665
+ return true;
666
+ }
667
+ /**
668
+ * Whether parameters are equal (have equal properties)
669
+ * Hint: functions will be ignored
670
+ */
671
+ static isEqual(a, b) {
672
+ return Helper.isSubset(a, b) && Helper.isSubset(b, a);
673
+ }
674
+ /**
675
+ * First level difference of two objects
676
+ * Hint: functions will be ignored and when an array or an object is different the hole array or object will used as property value
677
+ *
678
+ * @returns Record<string, any> Object with properties from first object
679
+ */
680
+ static getDiff(a, b, clone = true) {
681
+ const result = {};
682
+ let firstObject = a;
683
+ if (clone) {
684
+ firstObject = JSON.parse(JSON.stringify(a));
685
+ }
686
+ for (const [key, value] of Object.entries(firstObject)) {
687
+ if (!Helper.isSubset(value, b[key])) {
688
+ result[key] = value;
689
+ }
690
+ }
691
+ return result;
692
+ }
693
+ /**
694
+ * Check if propertie is type of HTMLCollection or NodeList
695
+ * See https://stackoverflow.com/a/7238344
696
+ *
697
+ * @param nodes
698
+ */
699
+ static isNodeList(nodes) {
700
+ try {
701
+ const stringRepr = Object.prototype.toString.call(nodes);
702
+ return (typeof nodes === 'object' &&
703
+ /^\[object (HTMLCollection|NodeList|Object)\]$/.test(stringRepr) &&
704
+ typeof nodes.length === 'number' &&
705
+ (nodes.length === 0 || (typeof nodes[0] === 'object' && nodes[0].nodeType > 0)));
706
+ }
707
+ catch (e) {
708
+ return false;
709
+ }
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Image service
715
+ */
716
+ class ImageService {
717
+ /**
718
+ * Preaload images (for lazy handling)
719
+ *
720
+ * @param options
721
+ * @protected
722
+ */
723
+ preLoadImages(options) {
724
+ // Init configuration
725
+ const config = Object.assign({ doneFunnction: undefined, elements: document, regExp: undefined }, options);
726
+ return new Promise((resolve) => {
727
+ var _a;
728
+ // Convert element(s) into array
729
+ if (!Array.isArray(config.elements)) {
730
+ // NodeList or HTMLCollection
731
+ if (Helper.isNodeList(config.elements)) {
732
+ config.elements = Array.from(config.elements);
733
+ }
734
+ // Single element
735
+ else {
736
+ config.elements = [config.elements];
737
+ }
738
+ }
739
+ // Init loading
740
+ const promises = [];
741
+ // Get all images
742
+ let images = [];
743
+ config.elements.forEach((element) => {
744
+ images = images.concat(Array.from(element.getElementsByTagName('img')));
745
+ });
746
+ // Prepare images
747
+ for (let image of images) {
748
+ const src = image.getAttribute('src');
749
+ // Check if changes are necessary and possible
750
+ if ((image.complete && image.naturalHeight !== 0) || // already laoded
751
+ !src || // Image has no src
752
+ ((_a = config.regExp) === null || _a === void 0 ? void 0 : _a.test(src)) // Test if src matches regular expression
753
+ ) {
754
+ continue;
755
+ }
756
+ // Replace lazy loading image with immediately loading image
757
+ if (image.getAttribute('loading') === 'lazy') {
758
+ const newImage = document.createElement('img');
759
+ Array.from(image.attributes).forEach((attr) => {
760
+ if (attr.name !== 'loading' && attr.name !== 'src') {
761
+ newImage.setAttribute(attr.name, attr.value);
762
+ }
763
+ });
764
+ image.replaceWith(newImage);
765
+ image = newImage;
766
+ }
767
+ // Wait for laoding
768
+ promises.push(this.loadImage(image, src));
769
+ }
770
+ // Wait until all images loaded
771
+ Promise.all(promises)
772
+ .catch((e) => console.error('Error during image loading', e))
773
+ .finally(() => __awaiter(this, void 0, void 0, function* () {
774
+ // Return result
775
+ let result;
776
+ if (config.doneFunnction) {
777
+ result = yield config.doneFunction();
778
+ }
779
+ resolve(result);
780
+ }));
781
+ });
782
+ }
783
+ /**
784
+ * Load image for image element
785
+ *
786
+ * @param imageElement
787
+ * @param src
788
+ * @protected
789
+ */
790
+ loadImage(imageElement, src) {
791
+ return __awaiter(this, void 0, void 0, function* () {
792
+ src = src || imageElement.src;
793
+ return new Promise((resolve, reject) => {
794
+ imageElement.onload = () => resolve(imageElement);
795
+ imageElement.onerror = reject;
796
+ imageElement.src = src;
797
+ });
798
+ });
799
+ }
800
+ }
801
+ ImageService.ɵprov = i0.ɵɵdefineInjectable({ factory: function ImageService_Factory() { return new ImageService(); }, token: ImageService, providedIn: "root" });
802
+ ImageService.decorators = [
803
+ { type: Injectable, args: [{
804
+ providedIn: 'root',
805
+ },] }
806
+ ];
807
+
808
+ /**
809
+ * Set same height (maximum) to different elements
810
+ */
811
+ class MatchHeightDirective {
812
+ constructor(el, imageService, platformId) {
813
+ this.el = el;
814
+ this.imageService = imageService;
815
+ // Replace lazy loading images with immediate loading images
816
+ this.loadImages = true;
817
+ this.isBrowser = isPlatformBrowser(platformId);
818
+ }
819
+ onResize() {
820
+ this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
821
+ }
822
+ ngOnInit() {
823
+ this.matchHeight(this.el.nativeElement, this.ltMatchHeight);
824
+ }
825
+ matchHeight(parent, target) {
826
+ return __awaiter(this, void 0, void 0, function* () {
827
+ if (!parent) {
828
+ return;
829
+ }
830
+ let classNames;
831
+ if (!Array.isArray(target)) {
832
+ classNames = [target];
833
+ }
834
+ else {
835
+ classNames = target;
836
+ }
837
+ for (const className of classNames) {
838
+ // find all child structureElements with the selected class name
839
+ const children = parent.getElementsByClassName(className);
840
+ if (!children || children.length < 2) {
841
+ continue;
842
+ }
843
+ // Load images of children
844
+ if (this.loadImages) {
845
+ yield this.imageService.preLoadImages({ elements: children });
846
+ }
847
+ // reset all children height
848
+ Array.from(children).forEach((element) => {
849
+ element.style.height = 'initial';
850
+ });
851
+ // get all the child structureElements heights
852
+ const itemHeights = Array.from(children).map((element) => {
853
+ if (element.getBoundingClientRect) {
854
+ return element.getBoundingClientRect().height;
855
+ }
856
+ return 0;
857
+ });
858
+ // find out the tallest one
859
+ const maxHeight = itemHeights.reduce((prev, curr) => (curr > prev ? curr : prev), 0);
860
+ // update the child structureElements height to the tallest one
861
+ Array.from(children).forEach((element) => (element.style.height = `${maxHeight}px`));
862
+ }
863
+ });
864
+ }
865
+ }
866
+ MatchHeightDirective.decorators = [
867
+ { type: Directive, args: [{
868
+ selector: '[ltMatchHeight]',
869
+ },] }
870
+ ];
871
+ MatchHeightDirective.ctorParameters = () => [
872
+ { type: ElementRef },
873
+ { type: ImageService },
874
+ { type: String, decorators: [{ type: Inject, args: [PLATFORM_ID,] }] }
875
+ ];
876
+ MatchHeightDirective.propDecorators = {
877
+ ltMatchHeight: [{ type: Input }],
878
+ loadImages: [{ type: Input }],
879
+ onResize: [{ type: HostListener, args: ['window:resize',] }]
880
+ };
881
+
882
+ /**
883
+ * Reszise detection
884
+ * (inpsired by: https://stackblitz.com/edit/angular-resizable)
885
+ */
886
+ class ResizableDirective {
887
+ constructor(el) {
888
+ this.el = el;
889
+ this.resizableGrabWidth = 8;
890
+ this.resizableMinWidth = 10;
891
+ this.dragging = false;
892
+ }
893
+ ngOnInit() {
894
+ this.addListenerToDocument();
895
+ this.addListenerToElement();
896
+ }
897
+ mouseMoveG(evt) {
898
+ if (!this.dragging) {
899
+ return;
975
900
  }
976
- data = data[2] ? [data[1], data[2]] : [navigator.appName, navigator.appVersion, '-?'];
977
- version = userAgent.match(/version\/(\d+)/i);
978
- if (version !== null) {
979
- data.splice(1, 1, version[1]);
901
+ this.newWidth(evt.clientX - this.el.nativeElement.offsetLeft);
902
+ evt.stopPropagation();
903
+ }
904
+ newWidth(wid) {
905
+ const result = Math.max(this.resizableMinWidth, wid);
906
+ this.el.nativeElement.style.width = result + 'px';
907
+ }
908
+ mouseDown(evt) {
909
+ if (this.inDragRegion(evt)) {
910
+ this.dragging = true;
911
+ this.preventGlobalMouseEvents();
912
+ evt.stopPropagation();
980
913
  }
981
- return {
982
- name: data[0],
983
- version: data[1],
984
- };
985
914
  }
986
- /**
987
- * Whether first parameter is a deep subset of second parameter
988
- * Hint: functions will be ignored
989
- */
990
- static isSubset(a, b) {
991
- // Equal
992
- if (a === b) {
993
- return true;
915
+ restoreGlobalMouseEvents() {
916
+ document.body.style['pointer-events'] = 'auto';
917
+ }
918
+ mouseUpG(evt) {
919
+ if (!this.dragging) {
920
+ return;
994
921
  }
995
- // Different type
996
- if (typeof a !== typeof b) {
997
- return false;
922
+ this.restoreGlobalMouseEvents();
923
+ this.dragging = false;
924
+ evt.stopPropagation();
925
+ }
926
+ mouseMove(evt) {
927
+ if (this.inDragRegion(evt) || this.dragging) {
928
+ this.el.nativeElement.style.cursor = 'col-resize';
998
929
  }
999
- // Process array
1000
- if (Array.isArray(a)) {
1001
- for (const [key, value] of Object.entries(a)) {
1002
- if (Helper.isSubset(value, b[key])) {
1003
- return false;
1004
- }
1005
- }
1006
- return true;
930
+ else {
931
+ this.el.nativeElement.style.cursor = 'default';
1007
932
  }
1008
- // Process object
1009
- if (typeof a === 'object') {
1010
- const plain = JSON.parse(JSON.stringify(a));
1011
- for (const [key, value] of Object.entries(plain)) {
1012
- if (Helper.isSubset(value, b[key])) {
1013
- return false;
1014
- }
1015
- }
1016
- return true;
933
+ }
934
+ preventGlobalMouseEvents() {
935
+ document.body.style['pointer-events'] = 'none';
936
+ }
937
+ inDragRegion(evt) {
938
+ return this.el.nativeElement.clientWidth - evt.clientX + this.el.nativeElement.offsetLeft < this.resizableGrabWidth;
939
+ }
940
+ addListenerToDocument() {
941
+ document.addEventListener('mousemove', (e) => {
942
+ this.mouseMoveG(e);
943
+ }, true);
944
+ document.addEventListener('mouseup', (e) => {
945
+ this.mouseUpG(e);
946
+ }, true);
947
+ }
948
+ addListenerToElement() {
949
+ this.el.nativeElement.addEventListener('mousedown', (e) => {
950
+ this.mouseDown(e);
951
+ }, true);
952
+ this.el.nativeElement.addEventListener('mousemove', (e) => {
953
+ this.mouseMove(e);
954
+ }, true);
955
+ }
956
+ }
957
+ ResizableDirective.decorators = [
958
+ { type: Directive, args: [{
959
+ selector: '[ltResizable]',
960
+ },] }
961
+ ];
962
+ ResizableDirective.ctorParameters = () => [
963
+ { type: ElementRef }
964
+ ];
965
+ ResizableDirective.propDecorators = {
966
+ resizableGrabWidth: [{ type: Input }],
967
+ resizableMinWidth: [{ type: Input }]
968
+ };
969
+
970
+ /**
971
+ * Truncate string with ellipses
972
+ */
973
+ class EllipsesPipe {
974
+ transform(value, limit) {
975
+ if (!value) {
976
+ return null;
1017
977
  }
1018
- // Else
1019
- return true;
978
+ return value.length > limit ? value.substring(0, limit) + '...' : value;
1020
979
  }
1021
- /**
1022
- * Whether parameters are equal (have equal properties)
1023
- * Hint: functions will be ignored
1024
- */
1025
- static isEqual(a, b) {
1026
- return Helper.isSubset(a, b) && Helper.isSubset(b, a);
980
+ }
981
+ EllipsesPipe.decorators = [
982
+ { type: Pipe, args: [{
983
+ name: 'ltEllipses',
984
+ },] }
985
+ ];
986
+
987
+ /**
988
+ * Declarate html as safe
989
+ */
990
+ class SafeHtmlPipe {
991
+ constructor(sanitized) {
992
+ this.sanitized = sanitized;
993
+ }
994
+ transform(value) {
995
+ return this.sanitized.bypassSecurityTrustHtml(value);
1027
996
  }
997
+ }
998
+ SafeHtmlPipe.decorators = [
999
+ { type: Pipe, args: [{ name: 'ltSafeHtml' },] }
1000
+ ];
1001
+ SafeHtmlPipe.ctorParameters = () => [
1002
+ { type: DomSanitizer }
1003
+ ];
1004
+
1005
+ // Imported and exported elements
1006
+ const elements = [
1007
+ // Directives
1008
+ ContextMenuDirective,
1009
+ LazyLoadDirective,
1010
+ MatchHeightDirective,
1011
+ ResizableDirective,
1012
+ // Pipes
1013
+ EllipsesPipe,
1014
+ SafeHtmlPipe,
1015
+ ];
1016
+ /**
1017
+ * Base module
1018
+ */
1019
+ class BaseModule {
1028
1020
  /**
1029
- * First level difference of two objects
1030
- * Hint: functions will be ignored and when an array or an object is different the hole array or object will used as property value
1031
- *
1032
- * @returns Record<string, any> Object with properties from first object
1021
+ * Set configuration of base module in root module of the app
1033
1022
  */
1034
- static getDiff(a, b, clone = true) {
1035
- const result = {};
1036
- let firstObject = a;
1037
- if (clone) {
1038
- firstObject = JSON.parse(JSON.stringify(a));
1023
+ static forRoot(baseModuleConfig = {}) {
1024
+ // Default config
1025
+ const config = Object.assign({ apiUrl: 'localhost:3000', authGuardRedirectUrl: '/auth', logging: false, version: null, prefix: null, scrollDetectionOffset: 200, scrollOffset: 100, storageType: 'local' }, baseModuleConfig);
1026
+ // Prepare providers
1027
+ const providers = [
1028
+ {
1029
+ provide: BASE_MODULE_CONFIG,
1030
+ useValue: baseModuleConfig,
1031
+ },
1032
+ ];
1033
+ // Add apollo features if API URL is set
1034
+ if (baseModuleConfig.apiUrl) {
1035
+ providers.push({
1036
+ provide: APOLLO_OPTIONS,
1037
+ useFactory: apolloOptionsFactory,
1038
+ deps: [BASE_MODULE_CONFIG, HttpLink, AuthService],
1039
+ });
1039
1040
  }
1040
- for (const [key, value] of Object.entries(firstObject)) {
1041
- if (!Helper.isSubset(value, b[key])) {
1042
- result[key] = value;
1043
- }
1041
+ else if (config.logging) {
1042
+ console.log('apiUrl is missing, ApolloLink is not initialized');
1044
1043
  }
1045
- return result;
1044
+ // Return root module
1045
+ return {
1046
+ ngModule: BaseModule,
1047
+ providers,
1048
+ };
1046
1049
  }
1047
1050
  }
1051
+ BaseModule.decorators = [
1052
+ { type: NgModule, args: [{
1053
+ declarations: elements,
1054
+ exports: elements,
1055
+ },] }
1056
+ ];
1048
1057
 
1049
1058
  /**
1050
1059
  * Standard class
@@ -2230,37 +2239,16 @@ class ScrollService {
2230
2239
  * Scroll done promise
2231
2240
  */
2232
2241
  scrollDone(options = {}) {
2233
- // Init configuration
2234
- const config = Object.assign({ doneFunction: undefined, timeout: undefined }, options);
2235
- // Return promise for scroll done
2236
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
2237
- // Init variables
2238
- let rejected = false;
2239
- let timeoutId;
2240
- // Set timeout handling if necessary
2241
- if (config.timeout) {
2242
- timeoutId = window.setTimeout(() => {
2243
- rejected = true;
2244
- reject(new Error('Timeout for scrollDone after:' + config.timeout));
2245
- }, config.timeout);
2246
- }
2242
+ return __awaiter(this, void 0, void 0, function* () {
2243
+ // Init configuration
2244
+ const config = Object.assign({ doneFunction: undefined }, options);
2247
2245
  // Wait for scroll done
2248
2246
  yield this.scrollDoneDetection();
2249
- // Resolve promise if not already rejected
2250
- if (!rejected) {
2251
- // Stop timeout if set
2252
- if (timeoutId) {
2253
- window.clearTimeout(timeoutId);
2254
- }
2255
- // Get result (if doneFunction is set)
2256
- let result;
2257
- if (config.doneFunction) {
2258
- result = yield config.doneFunction();
2259
- }
2260
- // Scroll done
2261
- resolve(result);
2247
+ // Get result
2248
+ if (config.doneFunction) {
2249
+ return config.doneFunction();
2262
2250
  }
2263
- }));
2251
+ });
2264
2252
  }
2265
2253
  /**
2266
2254
  * Scroll done detecton
@@ -2310,23 +2298,79 @@ class SEOService {
2310
2298
  this.titleService = titleService;
2311
2299
  this.metaTagService = metaTagService;
2312
2300
  }
2313
- initPageForSEO(title, description, keywords) {
2314
- this.setPageTitle(title);
2315
- this.setPageDescription(description);
2316
- if (keywords) {
2317
- this.setPageKeywords(keywords);
2301
+ initPageForSEO(input) {
2302
+ this.setGeneralData(input);
2303
+ this.setTwitterData(input);
2304
+ this.setTwitterData(input);
2305
+ this.setFacebookData(input);
2306
+ if (input.keywords) {
2307
+ this.setPageKeywords(input.keywords);
2318
2308
  }
2319
2309
  }
2320
- setPageTitle(title) {
2321
- this.titleService.setTitle(title);
2322
- this.metaTagService.updateTag({ property: 'og:title', content: title });
2323
- this.metaTagService.updateTag({ name: 'twitter:title', content: title });
2310
+ /**
2311
+ * Set general metadata
2312
+ *
2313
+ * @param data
2314
+ * @private
2315
+ */
2316
+ setGeneralData(data) {
2317
+ this.titleService.setTitle(data.title);
2318
+ this.metaTagService.updateTag({ name: 'description', content: data.description });
2319
+ }
2320
+ /**
2321
+ * Set metadata for twitter
2322
+ *
2323
+ * @param data
2324
+ * @private
2325
+ */
2326
+ setTwitterData(data) {
2327
+ this.metaTagService.updateTag({ name: 'twitter:title', content: data.title });
2328
+ this.metaTagService.updateTag({ name: 'twitter:description', content: data.description });
2329
+ this.metaTagService.updateTag({
2330
+ property: 'twitter:url',
2331
+ content: data.url ? data.url : window.location.href,
2332
+ });
2333
+ if (data.image) {
2334
+ this.metaTagService.updateTag({
2335
+ property: 'twitter:image',
2336
+ content: data.image,
2337
+ });
2338
+ this.metaTagService.updateTag({
2339
+ property: 'twitter:card',
2340
+ content: 'summary_large_image',
2341
+ });
2342
+ }
2324
2343
  }
2325
- setPageDescription(description) {
2326
- this.metaTagService.updateTag({ name: 'description', content: description });
2327
- this.metaTagService.updateTag({ property: 'og:description', content: description });
2328
- this.metaTagService.updateTag({ name: 'twitter:description', content: description });
2344
+ /**
2345
+ * Set metadata for facebook
2346
+ *
2347
+ * @param data
2348
+ * @private
2349
+ */
2350
+ setFacebookData(data) {
2351
+ this.metaTagService.updateTag({ property: 'og:title', content: data.title });
2352
+ this.metaTagService.updateTag({ property: 'og:description', content: data.description });
2353
+ this.metaTagService.updateTag({
2354
+ property: 'og:type',
2355
+ content: 'website',
2356
+ });
2357
+ this.metaTagService.updateTag({
2358
+ property: 'og:url',
2359
+ content: data.url ? data.url : window.location.href,
2360
+ });
2361
+ if (data.image) {
2362
+ this.metaTagService.updateTag({
2363
+ property: 'og:image',
2364
+ content: data.image,
2365
+ });
2366
+ }
2329
2367
  }
2368
+ /**
2369
+ * Set page keywords
2370
+ *
2371
+ * @param keywords
2372
+ * @private
2373
+ */
2330
2374
  setPageKeywords(keywords) {
2331
2375
  this.metaTagService.updateTag({ name: 'keywords', content: keywords });
2332
2376
  }