valtech-components 2.0.991 → 2.0.993
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/comment/comment.component.mjs +3 -3
- package/esm2022/lib/components/organisms/notifications-view/notifications-view.component.mjs +736 -0
- package/esm2022/lib/components/organisms/notifications-view/notifications-view.i18n.mjs +76 -0
- package/esm2022/lib/components/organisms/notifications-view/notifications.routes.mjs +32 -0
- package/esm2022/lib/components/organisms/notifications-view/types.mjs +2 -0
- package/esm2022/lib/services/preferences/preferences.service.mjs +1 -10
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +4 -1
- package/fesm2022/valtech-components.mjs +984 -175
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/notifications-view/notifications-view.component.d.ts +168 -0
- package/lib/components/organisms/notifications-view/notifications-view.i18n.d.ts +9 -0
- package/lib/components/organisms/notifications-view/notifications.routes.d.ts +27 -0
- package/lib/components/organisms/notifications-view/types.d.ts +52 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +3 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, untracked, Injector, isSignal, ViewEncapsulation } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable,
|
|
3
|
+
import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, race, timer, shareReplay, forkJoin } from 'rxjs';
|
|
4
4
|
import { catchError, switchMap, finalize, filter, take, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
|
|
5
5
|
import * as i1$3 from '@angular/common/http';
|
|
6
6
|
import { provideHttpClient, withInterceptors, HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
@@ -25,7 +25,7 @@ import { getApps, getApp, initializeApp as initializeApp$1 } from 'firebase/app'
|
|
|
25
25
|
import * as i2$1 from '@ionic/angular/standalone';
|
|
26
26
|
import { AlertController, IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonTextarea, IonDatetime, IonInput, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, IonDatetimeButton, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
|
|
27
27
|
import { addIcons } from 'ionicons';
|
|
28
|
-
import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, menuOutline } from 'ionicons/icons';
|
|
28
|
+
import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, notificationsOffOutline, menuOutline } from 'ionicons/icons';
|
|
29
29
|
import { Browser } from '@capacitor/browser';
|
|
30
30
|
import * as i1$6 from '@angular/platform-browser';
|
|
31
31
|
import { DomSanitizer, Meta, Title } from '@angular/platform-browser';
|
|
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
|
|
|
54
54
|
* Current version of valtech-components.
|
|
55
55
|
* This is automatically updated during the publish process.
|
|
56
56
|
*/
|
|
57
|
-
const VERSION = '2.0.
|
|
57
|
+
const VERSION = '2.0.993';
|
|
58
58
|
|
|
59
59
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
60
60
|
let isRefreshing = false;
|
|
@@ -21657,7 +21657,7 @@ class CommentComponent {
|
|
|
21657
21657
|
</div>
|
|
21658
21658
|
}
|
|
21659
21659
|
</div>
|
|
21660
|
-
`, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}:host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(.6875rem,.8125rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(.6875rem,.75rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(.6875rem,.6875rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(.6875rem,.625rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(.6875rem,.5625rem)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"], dependencies: [{ kind: "component", type: CommentComponent, selector: "val-comment", inputs: ["props"], outputs: ["authorClick", "reactionClick", "actionClick", "menuItemClick", "loadMoreClick", "collapseToggle"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: 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: IonPopover, selector: "ion-popover" }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
21660
|
+
`, isInline: true, styles: [":host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(11px,14px - 1px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(11px,14px - 2px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(11px,14px - 3px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(11px,14px - 4px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(11px,14px - 5px)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"], dependencies: [{ kind: "component", type: CommentComponent, selector: "val-comment", inputs: ["props"], outputs: ["authorClick", "reactionClick", "actionClick", "menuItemClick", "loadMoreClick", "collapseToggle"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: 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: IonPopover, selector: "ion-popover" }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
|
|
21661
21661
|
}
|
|
21662
21662
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CommentComponent, decorators: [{
|
|
21663
21663
|
type: Component,
|
|
@@ -21847,7 +21847,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
21847
21847
|
</div>
|
|
21848
21848
|
}
|
|
21849
21849
|
</div>
|
|
21850
|
-
`, styles: ["
|
|
21850
|
+
`, styles: [":host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(11px,14px - 1px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(11px,14px - 2px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(11px,14px - 3px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(11px,14px - 4px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(11px,14px - 5px)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"] }]
|
|
21851
21851
|
}], propDecorators: { props: [{
|
|
21852
21852
|
type: Input
|
|
21853
21853
|
}], authorClick: [{
|
|
@@ -41737,7 +41737,7 @@ const PROFILE_VIEW_I18N = {
|
|
|
41737
41737
|
const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
41738
41738
|
const HANDLE_MIN = 3;
|
|
41739
41739
|
const HANDLE_MAX = 20;
|
|
41740
|
-
const DEFAULT_NAMESPACE$
|
|
41740
|
+
const DEFAULT_NAMESPACE$d = 'Settings.Profile';
|
|
41741
41741
|
/**
|
|
41742
41742
|
* `val-profile-view` — vista Perfil full-feature autocontenida (organism).
|
|
41743
41743
|
*
|
|
@@ -41787,7 +41787,7 @@ class ProfileViewComponent {
|
|
|
41787
41787
|
showPhone: merged.showPhone ?? true,
|
|
41788
41788
|
showShareCta: merged.showShareCta ?? true,
|
|
41789
41789
|
profileBaseUrl: merged.profileBaseUrl ?? '',
|
|
41790
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
41790
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$d,
|
|
41791
41791
|
onSaved: merged.onSaved,
|
|
41792
41792
|
onAvatarUploaded: merged.onAvatarUploaded,
|
|
41793
41793
|
};
|
|
@@ -42489,15 +42489,6 @@ class PreferencesService {
|
|
|
42489
42489
|
this._updateInFlight = false;
|
|
42490
42490
|
const destroyRef = inject(DestroyRef);
|
|
42491
42491
|
destroyRef.onDestroy(() => this.unbind());
|
|
42492
|
-
// Seed `_fontSize` con el valor que FontSizeService ya restauró desde
|
|
42493
|
-
// localStorage en su constructor (providedIn root → ya corrió al inyectarlo
|
|
42494
|
-
// acá). Sin esto, el signal arranca en 'medium' y el effect de abajo —que
|
|
42495
|
-
// dispara al marcarse `synced`— forzaría 'medium' pisando el tamaño
|
|
42496
|
-
// restaurado cuando el snapshot Firestore NO trae `fontSize` (user sin ese
|
|
42497
|
-
// campo en el mirror). Bug: cambias el tamaño, refrescas y se pierde.
|
|
42498
|
-
if (this.fontSizeService) {
|
|
42499
|
-
this._fontSize.set(this.fontSizeService.size());
|
|
42500
|
-
}
|
|
42501
42492
|
// Auto-bind al user actual. effect() corre en injection context (constructor OK).
|
|
42502
42493
|
// allowSignalWrites: bindToUser/unbind escriben `_synced` (señal interna sin
|
|
42503
42494
|
// dependencias del propio effect) → seguro y necesario.
|
|
@@ -42712,7 +42703,7 @@ const PREFERENCES_VIEW_I18N = {
|
|
|
42712
42703
|
},
|
|
42713
42704
|
};
|
|
42714
42705
|
|
|
42715
|
-
const DEFAULT_NAMESPACE$
|
|
42706
|
+
const DEFAULT_NAMESPACE$c = 'Settings.General';
|
|
42716
42707
|
const DEFAULT_LANGUAGES = ['es', 'en'];
|
|
42717
42708
|
/**
|
|
42718
42709
|
* `val-preferences-view` — vista de preferencias de UI cross-app (tema +
|
|
@@ -42749,7 +42740,7 @@ class PreferencesViewComponent {
|
|
|
42749
42740
|
showLanguage: merged.showLanguage ?? true,
|
|
42750
42741
|
showFontSize: merged.showFontSize ?? true,
|
|
42751
42742
|
supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
|
|
42752
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
42743
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$c,
|
|
42753
42744
|
onThemeChanged: merged.onThemeChanged,
|
|
42754
42745
|
onLanguageChanged: merged.onLanguageChanged,
|
|
42755
42746
|
onFontSizeChanged: merged.onFontSizeChanged,
|
|
@@ -44561,7 +44552,7 @@ const SECURITY_VIEW_I18N = {
|
|
|
44561
44552
|
};
|
|
44562
44553
|
|
|
44563
44554
|
addIcons({ laptopOutline, phonePortraitOutline });
|
|
44564
|
-
const DEFAULT_NAMESPACE$
|
|
44555
|
+
const DEFAULT_NAMESPACE$b = 'Settings.Security';
|
|
44565
44556
|
/**
|
|
44566
44557
|
* `val-security-view` — vista Seguridad full-feature autocontenida (organism).
|
|
44567
44558
|
*
|
|
@@ -44610,7 +44601,7 @@ class SecurityViewComponent {
|
|
|
44610
44601
|
showChangeEmail: merged.showChangeEmail ?? true,
|
|
44611
44602
|
showMfa: merged.showMfa ?? true,
|
|
44612
44603
|
showPolicies: merged.showPolicies ?? true,
|
|
44613
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
44604
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
|
|
44614
44605
|
handleDeepLinks: merged.handleDeepLinks ?? true,
|
|
44615
44606
|
homeRoute: merged.homeRoute ?? '/app/home',
|
|
44616
44607
|
onMfaCompleted: merged.onMfaCompleted,
|
|
@@ -45516,7 +45507,7 @@ const CREATE_ORG_MODAL_I18N = {
|
|
|
45516
45507
|
},
|
|
45517
45508
|
};
|
|
45518
45509
|
|
|
45519
|
-
const DEFAULT_NAMESPACE$
|
|
45510
|
+
const DEFAULT_NAMESPACE$a = 'CreateOrgModal';
|
|
45520
45511
|
/**
|
|
45521
45512
|
* `val-create-org-modal` — modal de creación de organización (organism
|
|
45522
45513
|
* compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
|
|
@@ -45542,10 +45533,10 @@ class CreateOrgModalComponent {
|
|
|
45542
45533
|
this.toast = inject(ToastService);
|
|
45543
45534
|
this.errors = inject(ValtechErrorService);
|
|
45544
45535
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45545
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45536
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$a;
|
|
45546
45537
|
this._busy = signal(false);
|
|
45547
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45548
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45538
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
|
|
45539
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$a, CREATE_ORG_MODAL_I18N);
|
|
45549
45540
|
}
|
|
45550
45541
|
this.formMeta = signal(this.buildFormMeta());
|
|
45551
45542
|
effect(() => {
|
|
@@ -45781,7 +45772,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
|
|
|
45781
45772
|
},
|
|
45782
45773
|
};
|
|
45783
45774
|
|
|
45784
|
-
const DEFAULT_NAMESPACE$
|
|
45775
|
+
const DEFAULT_NAMESPACE$9 = 'Settings.DeleteAccount';
|
|
45785
45776
|
/**
|
|
45786
45777
|
* `val-delete-account-modal` — modal de eliminación de cuenta (organism,
|
|
45787
45778
|
* cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -45807,7 +45798,7 @@ class DeleteAccountModalComponent {
|
|
|
45807
45798
|
this.i18n = inject(I18nService);
|
|
45808
45799
|
this.toast = inject(ToastService);
|
|
45809
45800
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
45810
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
45801
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$9;
|
|
45811
45802
|
this.hasPassword = signal(null);
|
|
45812
45803
|
this.codeSent = signal(false);
|
|
45813
45804
|
this.formState = signal(ComponentStates.ENABLED);
|
|
@@ -45874,8 +45865,8 @@ class DeleteAccountModalComponent {
|
|
|
45874
45865
|
],
|
|
45875
45866
|
actions: this.submitBtn('delete-code-submit', 'confirmCta'),
|
|
45876
45867
|
}));
|
|
45877
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
45878
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
45868
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
|
|
45869
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$9, DELETE_ACCOUNT_MODAL_I18N);
|
|
45879
45870
|
}
|
|
45880
45871
|
}
|
|
45881
45872
|
ngOnInit() {
|
|
@@ -46076,7 +46067,7 @@ const ORG_INFO_SHEET_I18N = {
|
|
|
46076
46067
|
},
|
|
46077
46068
|
};
|
|
46078
46069
|
|
|
46079
|
-
const DEFAULT_NAMESPACE$
|
|
46070
|
+
const DEFAULT_NAMESPACE$8 = 'OrgInfoSheet';
|
|
46080
46071
|
/**
|
|
46081
46072
|
* `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
|
|
46082
46073
|
* Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
|
|
@@ -46121,15 +46112,15 @@ class OrgInfoSheetComponent {
|
|
|
46121
46112
|
text: this.closeProps.text || this.resolvedCloseLabel(),
|
|
46122
46113
|
}));
|
|
46123
46114
|
addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
|
|
46124
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46125
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46115
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
|
|
46116
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$8, ORG_INFO_SHEET_I18N);
|
|
46126
46117
|
}
|
|
46127
46118
|
}
|
|
46128
46119
|
dismiss() {
|
|
46129
46120
|
this._modalRef?.dismiss(null, 'cancel');
|
|
46130
46121
|
}
|
|
46131
46122
|
tt(key) {
|
|
46132
|
-
return this.i18n.t(key, DEFAULT_NAMESPACE$
|
|
46123
|
+
return this.i18n.t(key, DEFAULT_NAMESPACE$8);
|
|
46133
46124
|
}
|
|
46134
46125
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
46135
46126
|
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
|
|
@@ -46296,7 +46287,7 @@ const SWITCH_ORG_MODAL_I18N = {
|
|
|
46296
46287
|
},
|
|
46297
46288
|
};
|
|
46298
46289
|
|
|
46299
|
-
const DEFAULT_NAMESPACE$
|
|
46290
|
+
const DEFAULT_NAMESPACE$7 = 'Settings.SwitchOrg';
|
|
46300
46291
|
addIcons({ businessOutline, checkmarkCircleOutline });
|
|
46301
46292
|
/**
|
|
46302
46293
|
* `val-switch-org-modal` — modal de cambio de organización activa (organism
|
|
@@ -46322,7 +46313,7 @@ class SwitchOrgModalComponent {
|
|
|
46322
46313
|
this.orgSwitch = inject(OrgSwitchService);
|
|
46323
46314
|
this.errors = inject(ValtechErrorService);
|
|
46324
46315
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
46325
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
46316
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$7;
|
|
46326
46317
|
this.orgs = signal([]);
|
|
46327
46318
|
this.loading = signal(false);
|
|
46328
46319
|
this.query = signal('');
|
|
@@ -46338,8 +46329,8 @@ class SwitchOrgModalComponent {
|
|
|
46338
46329
|
return list;
|
|
46339
46330
|
return list.filter(o => o.name.toLowerCase().includes(q));
|
|
46340
46331
|
});
|
|
46341
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
46342
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
46332
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
|
|
46333
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$7, SWITCH_ORG_MODAL_I18N);
|
|
46343
46334
|
}
|
|
46344
46335
|
this.loadOrgs();
|
|
46345
46336
|
}
|
|
@@ -46657,7 +46648,7 @@ const ACCOUNT_VIEW_I18N = {
|
|
|
46657
46648
|
};
|
|
46658
46649
|
|
|
46659
46650
|
addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
|
|
46660
|
-
const DEFAULT_NAMESPACE$
|
|
46651
|
+
const DEFAULT_NAMESPACE$6 = 'Settings.Account';
|
|
46661
46652
|
const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
|
|
46662
46653
|
/**
|
|
46663
46654
|
* `val-account-view` — vista Cuenta full-feature autocontenida (organism).
|
|
@@ -46698,7 +46689,7 @@ class AccountViewComponent {
|
|
|
46698
46689
|
showNewOrgCta: merged.showNewOrgCta ?? true,
|
|
46699
46690
|
showLogout: merged.showLogout ?? true,
|
|
46700
46691
|
showDeleteAccount: merged.showDeleteAccount ?? true,
|
|
46701
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$
|
|
46692
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$6,
|
|
46702
46693
|
manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
|
|
46703
46694
|
onOrgSwitched: merged.onOrgSwitched,
|
|
46704
46695
|
onOrgCreated: merged.onOrgCreated,
|
|
@@ -47423,7 +47414,7 @@ const EDIT_ORG_MODAL_I18N = {
|
|
|
47423
47414
|
},
|
|
47424
47415
|
};
|
|
47425
47416
|
|
|
47426
|
-
const DEFAULT_NAMESPACE$
|
|
47417
|
+
const DEFAULT_NAMESPACE$5 = 'EditOrgModal';
|
|
47427
47418
|
/**
|
|
47428
47419
|
* `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
|
|
47429
47420
|
* Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
|
|
@@ -47450,10 +47441,10 @@ class EditOrgModalComponent {
|
|
|
47450
47441
|
/** Organización a editar (pasada por `componentProps`). */
|
|
47451
47442
|
this.org = null;
|
|
47452
47443
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47453
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47444
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$5;
|
|
47454
47445
|
this.saving = signal(false);
|
|
47455
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47456
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47446
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
|
|
47447
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$5, EDIT_ORG_MODAL_I18N);
|
|
47457
47448
|
}
|
|
47458
47449
|
this.formMeta = signal(this.buildFormMeta());
|
|
47459
47450
|
effect(() => {
|
|
@@ -47691,7 +47682,7 @@ const INVITE_MEMBER_MODAL_I18N = {
|
|
|
47691
47682
|
},
|
|
47692
47683
|
};
|
|
47693
47684
|
|
|
47694
|
-
const DEFAULT_NAMESPACE$
|
|
47685
|
+
const DEFAULT_NAMESPACE$4 = 'InviteModal';
|
|
47695
47686
|
addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
|
|
47696
47687
|
/**
|
|
47697
47688
|
* `val-invite-member-modal` — invitación de miembros a una organización.
|
|
@@ -47722,7 +47713,7 @@ class InviteMemberModalComponent {
|
|
|
47722
47713
|
/** Ids de miembros ya existentes (excluidos de los resultados). */
|
|
47723
47714
|
this.existingMemberIds = [];
|
|
47724
47715
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
47725
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
47716
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$4;
|
|
47726
47717
|
this.query = signal('');
|
|
47727
47718
|
this.searching = signal(false);
|
|
47728
47719
|
this.searchResults = signal([]);
|
|
@@ -47769,8 +47760,8 @@ class InviteMemberModalComponent {
|
|
|
47769
47760
|
state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
47770
47761
|
}));
|
|
47771
47762
|
this.searchSubject = new Subject();
|
|
47772
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
47773
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
47763
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
|
|
47764
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$4, INVITE_MEMBER_MODAL_I18N);
|
|
47774
47765
|
}
|
|
47775
47766
|
this.loadRoles();
|
|
47776
47767
|
this.roleSub = this.roleControl.valueChanges.subscribe(v => {
|
|
@@ -48259,7 +48250,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
|
|
|
48259
48250
|
},
|
|
48260
48251
|
};
|
|
48261
48252
|
|
|
48262
|
-
const DEFAULT_NAMESPACE$
|
|
48253
|
+
const DEFAULT_NAMESPACE$3 = 'Settings.MemberDetail';
|
|
48263
48254
|
/**
|
|
48264
48255
|
* `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
|
|
48265
48256
|
* permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
@@ -48296,7 +48287,7 @@ class MemberDetailModalComponent {
|
|
|
48296
48287
|
{ id: 'admin', name: 'admin' },
|
|
48297
48288
|
];
|
|
48298
48289
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48299
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48290
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$3;
|
|
48300
48291
|
this.working = signal(false);
|
|
48301
48292
|
this.currentRole = signal('');
|
|
48302
48293
|
this.roleControl = new FormControl('');
|
|
@@ -48336,8 +48327,8 @@ class MemberDetailModalComponent {
|
|
|
48336
48327
|
type: 'button',
|
|
48337
48328
|
state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
48338
48329
|
}));
|
|
48339
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48340
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48330
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
|
|
48331
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$3, MEMBER_DETAIL_MODAL_I18N);
|
|
48341
48332
|
}
|
|
48342
48333
|
}
|
|
48343
48334
|
ngOnInit() {
|
|
@@ -48674,7 +48665,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
|
|
|
48674
48665
|
},
|
|
48675
48666
|
};
|
|
48676
48667
|
|
|
48677
|
-
const DEFAULT_NAMESPACE$
|
|
48668
|
+
const DEFAULT_NAMESPACE$2 = 'Settings.TransferModal';
|
|
48678
48669
|
/**
|
|
48679
48670
|
* `val-transfer-ownership-modal` — selector de miembro para transferir la
|
|
48680
48671
|
* propiedad de la organización. Promovido desde `showcase` bajo el proceso de
|
|
@@ -48698,7 +48689,7 @@ class TransferOwnershipModalComponent {
|
|
|
48698
48689
|
/** Lista de miembros transferibles (todos excepto el owner actual). */
|
|
48699
48690
|
this.members = [];
|
|
48700
48691
|
/** Namespace i18n con que la vista resuelve sus textos. */
|
|
48701
|
-
this.i18nNamespace = DEFAULT_NAMESPACE$
|
|
48692
|
+
this.i18nNamespace = DEFAULT_NAMESPACE$2;
|
|
48702
48693
|
this.query = signal('');
|
|
48703
48694
|
this.selectedId = signal('');
|
|
48704
48695
|
this.filtered = computed(() => {
|
|
@@ -48710,8 +48701,8 @@ class TransferOwnershipModalComponent {
|
|
|
48710
48701
|
m.userId.toLowerCase().includes(q));
|
|
48711
48702
|
});
|
|
48712
48703
|
this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
|
|
48713
|
-
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$
|
|
48714
|
-
this.i18n.registerContent(DEFAULT_NAMESPACE$
|
|
48704
|
+
if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
|
|
48705
|
+
this.i18n.registerContent(DEFAULT_NAMESPACE$2, TRANSFER_OWNERSHIP_MODAL_I18N);
|
|
48715
48706
|
}
|
|
48716
48707
|
}
|
|
48717
48708
|
ngOnInit() {
|
|
@@ -49044,7 +49035,7 @@ const ORGANIZATION_VIEW_I18N = {
|
|
|
49044
49035
|
},
|
|
49045
49036
|
};
|
|
49046
49037
|
|
|
49047
|
-
const DEFAULT_NAMESPACE = 'Settings.Organization';
|
|
49038
|
+
const DEFAULT_NAMESPACE$1 = 'Settings.Organization';
|
|
49048
49039
|
const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
|
|
49049
49040
|
const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
|
|
49050
49041
|
/**
|
|
@@ -49101,7 +49092,7 @@ class OrganizationViewComponent {
|
|
|
49101
49092
|
showInvite: merged.showInvite ?? true,
|
|
49102
49093
|
showTransferOwnership: merged.showTransferOwnership ?? true,
|
|
49103
49094
|
showLeave: merged.showLeave ?? true,
|
|
49104
|
-
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
49095
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
|
|
49105
49096
|
viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
|
|
49106
49097
|
leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
|
|
49107
49098
|
onOrgEdited: merged.onOrgEdited,
|
|
@@ -50108,6 +50099,942 @@ function provideValtechOrganizationRoutes(opts) {
|
|
|
50108
50099
|
];
|
|
50109
50100
|
}
|
|
50110
50101
|
|
|
50102
|
+
/**
|
|
50103
|
+
* Refreshable Stream
|
|
50104
|
+
*
|
|
50105
|
+
* Helper estándar para vistas que consumen datos de Firestore. Soporta DOS
|
|
50106
|
+
* modos — el caller decide cuál vía la `factory`:
|
|
50107
|
+
*
|
|
50108
|
+
* - **one-shot** (recomendado por defecto): `() => from(svc.getAllOnce())`.
|
|
50109
|
+
* Carga una vez y completa. Más barato — sin listener vivo.
|
|
50110
|
+
* - **real-time**: `() => svc.getAll()`. Listener Firestore que auto-actualiza.
|
|
50111
|
+
* Solo donde importa (inbox de notificaciones, badges).
|
|
50112
|
+
*
|
|
50113
|
+
* En ambos modos la primera suscripción se gatea por `firebaseAuthReady`
|
|
50114
|
+
* (sesión de Firebase Auth lista) — así el listener/lectura nunca se adjunta
|
|
50115
|
+
* antes de que `request.auth` esté disponible en las reglas de Firestore, lo
|
|
50116
|
+
* que cierra la ventana de `permission-denied` en cold start de PWA.
|
|
50117
|
+
*
|
|
50118
|
+
* El `retry` con backoff corto es solo red de seguridad para reconexiones
|
|
50119
|
+
* transitorias (no para cubrir cold start — eso lo cubre el gate de auth).
|
|
50120
|
+
*/
|
|
50121
|
+
/**
|
|
50122
|
+
* Crea un stream refrescable a partir de una factory de Observable.
|
|
50123
|
+
*
|
|
50124
|
+
* DEBE llamarse en un injection context (field initializer o constructor) —
|
|
50125
|
+
* usa `toSignal`/`toObservable`/`inject` internamente.
|
|
50126
|
+
*
|
|
50127
|
+
* @param factory - Función que produce el Observable a consumir. El caller
|
|
50128
|
+
* decide el modo: `() => from(svc.getAllOnce())` (one-shot) o
|
|
50129
|
+
* `() => svc.getAll()` (real-time).
|
|
50130
|
+
* @param options - Configuración opcional (fallback, retry, gate).
|
|
50131
|
+
*
|
|
50132
|
+
* @example
|
|
50133
|
+
* ```typescript
|
|
50134
|
+
* // one-shot (recomendado por defecto)
|
|
50135
|
+
* private readonly stream = createRefreshableStream(
|
|
50136
|
+
* () => from(this.svc.getAllOnce()),
|
|
50137
|
+
* );
|
|
50138
|
+
*
|
|
50139
|
+
* // real-time (inbox)
|
|
50140
|
+
* private readonly stream = createRefreshableStream(
|
|
50141
|
+
* () => this.notifs.getAll(50),
|
|
50142
|
+
* );
|
|
50143
|
+
*
|
|
50144
|
+
* readonly items = this.stream.data;
|
|
50145
|
+
* readonly loading = this.stream.loading;
|
|
50146
|
+
*
|
|
50147
|
+
* ionViewWillEnter() {
|
|
50148
|
+
* this.pageRefresh.register(() => this.stream.reload());
|
|
50149
|
+
* }
|
|
50150
|
+
* ```
|
|
50151
|
+
*/
|
|
50152
|
+
function createRefreshableStream(factory, options) {
|
|
50153
|
+
const injector = options?.injector ?? inject(Injector);
|
|
50154
|
+
const fallback = (options?.fallback ?? null);
|
|
50155
|
+
const retryCount = options?.retryCount ?? 4;
|
|
50156
|
+
const retryBaseDelayMs = options?.retryBaseDelayMs ?? 500;
|
|
50157
|
+
const gateOnFirebaseAuth = options?.gateOnFirebaseAuth ?? true;
|
|
50158
|
+
const authGateTimeoutMs = options?.authGateTimeoutMs ?? 20000;
|
|
50159
|
+
const firstEmitTimeoutMs = options?.firstEmitTimeoutMs ?? 20000;
|
|
50160
|
+
// FirebaseService es opcional — sin él (apps sin Firebase) no se gatea.
|
|
50161
|
+
const firebase = gateOnFirebaseAuth ? injector.get(FirebaseService, null) : null;
|
|
50162
|
+
const _reload = signal(0);
|
|
50163
|
+
const _loading = signal(true);
|
|
50164
|
+
const _error = signal(false);
|
|
50165
|
+
// Gate de Firebase Auth: el stream no abre la primera suscripción hasta que
|
|
50166
|
+
// la sesión esté confirmada. `race` con un `timer` de seguridad — si la
|
|
50167
|
+
// sesión NUNCA se establece (token inválido, signInWithFirebase falló), el
|
|
50168
|
+
// gate igual se libera tras `authGateTimeoutMs` y la factory procede: si no
|
|
50169
|
+
// hay permiso terminará en estado `error`, jamás en skeleton infinito.
|
|
50170
|
+
// Tag los dos inputs del `race` para distinguir en logs cuál ganó: la sesión
|
|
50171
|
+
// de Firebase Auth (`auth-ready`) o el timeout de seguridad (`timeout`).
|
|
50172
|
+
const authGate$ = firebase
|
|
50173
|
+
? race(firebase.firebaseAuthReady$.pipe(map$1(() => 'auth-ready')), timer(authGateTimeoutMs).pipe(map$1(() => 'timeout'))).pipe(take(1), tap(winner => {
|
|
50174
|
+
if (winner === 'timeout') {
|
|
50175
|
+
console.warn(`[RefreshStream] auth gate resolved by TIMEOUT after ${authGateTimeoutMs}ms ` +
|
|
50176
|
+
'(Firebase Auth session never confirmed)');
|
|
50177
|
+
}
|
|
50178
|
+
else {
|
|
50179
|
+
console.log('[RefreshStream] auth gate resolved by firebaseAuthReady$ (session ready)');
|
|
50180
|
+
}
|
|
50181
|
+
}))
|
|
50182
|
+
: of(true);
|
|
50183
|
+
const data = toSignal(toObservable(_reload, { injector }).pipe(tap(() => {
|
|
50184
|
+
_loading.set(true);
|
|
50185
|
+
_error.set(false);
|
|
50186
|
+
}),
|
|
50187
|
+
// Esperar a Firebase Auth listo antes de cada (re)suscripción. Como
|
|
50188
|
+
// `firebaseAuthReady$` completa tras la primera emisión, las reloads
|
|
50189
|
+
// posteriores resuelven el gate de inmediato (auth ya está listo).
|
|
50190
|
+
switchMap(() => authGate$.pipe(switchMap(() => factory().pipe(tap(() => {
|
|
50191
|
+
_loading.set(false);
|
|
50192
|
+
_error.set(false);
|
|
50193
|
+
console.log('[RefreshStream] factory emitted first value — loading=false');
|
|
50194
|
+
}),
|
|
50195
|
+
// Backoff corto exponencial acotado — solo red de seguridad para
|
|
50196
|
+
// reconexiones transitorias, NO para cubrir cold start.
|
|
50197
|
+
retry({
|
|
50198
|
+
count: retryCount,
|
|
50199
|
+
delay: (_err, attempt) => timer(Math.min(retryBaseDelayMs * 2 ** (attempt - 1), 4000)),
|
|
50200
|
+
}),
|
|
50201
|
+
// Techo absoluto para la PRIMERA emisión. Si la factory no emite
|
|
50202
|
+
// ni valor ni error (servicio nunca inicializado → `EMPTY` →
|
|
50203
|
+
// silencio), `timeout` dispara un error → `catchError` → estado
|
|
50204
|
+
// `error`. Nunca skeleton perpetuo. Va DESPUÉS de `retry` para
|
|
50205
|
+
// que cuente el tiempo total, no por intento.
|
|
50206
|
+
timeout({ first: firstEmitTimeoutMs }), catchError(err => {
|
|
50207
|
+
_loading.set(false);
|
|
50208
|
+
_error.set(true);
|
|
50209
|
+
console.error(`[RefreshStream] factory failed/timed out → error state: ${err instanceof Error ? err.message : String(err)}`);
|
|
50210
|
+
return of(fallback);
|
|
50211
|
+
})))))), { initialValue: null, injector });
|
|
50212
|
+
return {
|
|
50213
|
+
data,
|
|
50214
|
+
loading: _loading.asReadonly(),
|
|
50215
|
+
error: _error.asReadonly(),
|
|
50216
|
+
reload: () => _reload.update(n => n + 1),
|
|
50217
|
+
};
|
|
50218
|
+
}
|
|
50219
|
+
|
|
50220
|
+
/**
|
|
50221
|
+
* Defaults i18n (es/en) embebidos en `val-notifications-view`. Auto-registrados
|
|
50222
|
+
* en el constructor del componente si el consumer no proveyó el namespace
|
|
50223
|
+
* (`Notifications` por default). SOLO incluye las keys que la vista usa.
|
|
50224
|
+
*
|
|
50225
|
+
* Portado del override `Notifications` (showcase) bajo el proceso de ADR-021.
|
|
50226
|
+
*/
|
|
50227
|
+
const NOTIFICATIONS_VIEW_I18N = {
|
|
50228
|
+
es: {
|
|
50229
|
+
pageTitle: 'Notificaciones',
|
|
50230
|
+
pageDescription: 'Tu bandeja de mensajes y actualizaciones',
|
|
50231
|
+
groupToday: 'Hoy',
|
|
50232
|
+
groupThisMonth: 'Este mes',
|
|
50233
|
+
groupEarlier: 'Anteriores',
|
|
50234
|
+
empty: 'Aún no tienes notificaciones',
|
|
50235
|
+
emptyHint: 'Activa las notificaciones para recibir actualizaciones, alertas y novedades importantes.',
|
|
50236
|
+
emptyCta: 'Ir a configuración',
|
|
50237
|
+
loadErrorTitle: 'No pudimos cargar tus notificaciones',
|
|
50238
|
+
loadErrorHint: 'Revisa tu conexión e inténtalo de nuevo. Tus notificaciones siguen guardadas.',
|
|
50239
|
+
offlineTitle: 'Sin conexión',
|
|
50240
|
+
offlineHint: 'No hay acceso a la red. Reconéctate e intenta de nuevo.',
|
|
50241
|
+
retryCta: 'Reintentar',
|
|
50242
|
+
pushOffTitle: 'No recibirás avisos',
|
|
50243
|
+
pushOffHint: 'Puedes leer tus mensajes aquí, pero las notificaciones push están desactivadas. Actívalas para recibir avisos en tu dispositivo.',
|
|
50244
|
+
pushOffCta: 'Activar notificaciones',
|
|
50245
|
+
loading: 'Cargando…',
|
|
50246
|
+
loadMore: 'Cargar más',
|
|
50247
|
+
seeMore: 'Ver más',
|
|
50248
|
+
seeLess: 'Ver menos',
|
|
50249
|
+
open: 'Ver',
|
|
50250
|
+
markAll: 'Marcar todas como leídas',
|
|
50251
|
+
unread: 'No leída',
|
|
50252
|
+
crossApp: 'Desde {app}',
|
|
50253
|
+
timeNow: 'ahora',
|
|
50254
|
+
timeMinutes: 'hace {n} min',
|
|
50255
|
+
timeHours: 'hace {n} h',
|
|
50256
|
+
timeDays: 'hace {n} d',
|
|
50257
|
+
markedAllSuccess: 'Notificaciones marcadas como leídas.',
|
|
50258
|
+
markedAllError: 'No se pudo marcar todas como leídas.',
|
|
50259
|
+
openError: 'No se pudo abrir la notificación.',
|
|
50260
|
+
},
|
|
50261
|
+
en: {
|
|
50262
|
+
pageTitle: 'Notifications',
|
|
50263
|
+
pageDescription: 'Your inbox of messages and updates',
|
|
50264
|
+
groupToday: 'Today',
|
|
50265
|
+
groupThisMonth: 'This month',
|
|
50266
|
+
groupEarlier: 'Earlier',
|
|
50267
|
+
empty: 'No notifications yet',
|
|
50268
|
+
emptyHint: 'Enable notifications to receive updates, alerts and important news.',
|
|
50269
|
+
emptyCta: 'Go to settings',
|
|
50270
|
+
loadErrorTitle: "We couldn't load your notifications",
|
|
50271
|
+
loadErrorHint: 'Check your connection and try again. Your notifications are still saved.',
|
|
50272
|
+
offlineTitle: 'No connection',
|
|
50273
|
+
offlineHint: 'No network access. Reconnect and try again.',
|
|
50274
|
+
retryCta: 'Retry',
|
|
50275
|
+
pushOffTitle: "You won't get alerts",
|
|
50276
|
+
pushOffHint: 'You can read your messages here, but push notifications are off. Turn them on to get alerts on your device.',
|
|
50277
|
+
pushOffCta: 'Enable notifications',
|
|
50278
|
+
loading: 'Loading…',
|
|
50279
|
+
loadMore: 'Load more',
|
|
50280
|
+
seeMore: 'See more',
|
|
50281
|
+
seeLess: 'See less',
|
|
50282
|
+
open: 'Open',
|
|
50283
|
+
markAll: 'Mark all as read',
|
|
50284
|
+
unread: 'Unread',
|
|
50285
|
+
crossApp: 'From {app}',
|
|
50286
|
+
timeNow: 'now',
|
|
50287
|
+
timeMinutes: '{n} min ago',
|
|
50288
|
+
timeHours: '{n} h ago',
|
|
50289
|
+
timeDays: '{n} d ago',
|
|
50290
|
+
markedAllSuccess: 'Notifications marked as read.',
|
|
50291
|
+
markedAllError: 'Could not mark all as read.',
|
|
50292
|
+
openError: 'Could not open notification.',
|
|
50293
|
+
},
|
|
50294
|
+
};
|
|
50295
|
+
|
|
50296
|
+
addIcons({
|
|
50297
|
+
alarmOutline,
|
|
50298
|
+
chatboxEllipsesOutline,
|
|
50299
|
+
chatbubbleOutline,
|
|
50300
|
+
checkmarkCircleOutline,
|
|
50301
|
+
checkmarkDoneOutline,
|
|
50302
|
+
cloudOfflineOutline,
|
|
50303
|
+
cogOutline,
|
|
50304
|
+
documentTextOutline,
|
|
50305
|
+
notificationsOffOutline,
|
|
50306
|
+
notificationsOutline,
|
|
50307
|
+
openOutline,
|
|
50308
|
+
personOutline,
|
|
50309
|
+
});
|
|
50310
|
+
const DEFAULT_NAMESPACE = 'Notifications';
|
|
50311
|
+
const DEFAULT_SETTINGS_PATH = '/app/settings/notifications';
|
|
50312
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
50313
|
+
const DEFAULT_MAX_LOAD = 50;
|
|
50314
|
+
/**
|
|
50315
|
+
* `val-notifications-view` — inbox de notificaciones full-feature autocontenido
|
|
50316
|
+
* (organism). Promovido desde `showcase` bajo el proceso de ADR-021.
|
|
50317
|
+
*
|
|
50318
|
+
* Separado de `/app/settings/notifications` (que controla permiso FCM + master
|
|
50319
|
+
* toggle). Aquí el user lee/abre/marca-leídas sus mensajes.
|
|
50320
|
+
*
|
|
50321
|
+
* - **Stream real-time** vía `NotificationsService.getAll(maxLoad)` envuelto en
|
|
50322
|
+
* `createRefreshableStream` (gateado por `firebaseAuthReady`, retry corto,
|
|
50323
|
+
* `firstEmitTimeoutMs: 15000`). Estados: loading (skeleton), error
|
|
50324
|
+
* (`val-empty-state` + reintentar), empty (`val-empty-state` + CTA), data
|
|
50325
|
+
* (lista agrupada Hoy / Este mes / Anteriores).
|
|
50326
|
+
* - **Abrir** una notif → `NotificationActionService.open()` (markRead +
|
|
50327
|
+
* switch-org + navegar/handoff cross-app). Hook `onNotificationOpen` opcional.
|
|
50328
|
+
* - **Marcar todas como leídas** (gateado por `enableMarkAll`).
|
|
50329
|
+
* - **CTA push-off** cuando el permiso del navegador no está concedido (gateado
|
|
50330
|
+
* por `showPushOffCta`); navega a `settingsNotificationsPath`.
|
|
50331
|
+
* - **Paginación soft** — slice local (`pageSize`, +`pageSize` por click) sobre el
|
|
50332
|
+
* cache del stream; el tope de fetch es `maxLoad`.
|
|
50333
|
+
* - **i18n auto-registrado** es/en bajo `i18nNamespace` (default `Notifications`).
|
|
50334
|
+
*
|
|
50335
|
+
* NO renderiza ion-content — vive dentro de val-page-wrapper. `Router` y
|
|
50336
|
+
* `ActivatedRoute` se inyectan `{ optional: true }` → funciona sin router
|
|
50337
|
+
* (embebida / Storybook). El forward-compat icon resolver lee `notif.payload.icon`
|
|
50338
|
+
* si existe (`{ type, value, color? }`), cae a un mapa por `notif.type`, luego a
|
|
50339
|
+
* `notifications-outline`.
|
|
50340
|
+
*/
|
|
50341
|
+
class NotificationsViewComponent {
|
|
50342
|
+
/** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
|
|
50343
|
+
get ns() {
|
|
50344
|
+
return this.resolvedConfig().i18nNamespace;
|
|
50345
|
+
}
|
|
50346
|
+
/** Paso de paginación soft (revela +N por "cargar más"). */
|
|
50347
|
+
get pageStep() {
|
|
50348
|
+
return this.resolvedConfig().pageSize;
|
|
50349
|
+
}
|
|
50350
|
+
constructor() {
|
|
50351
|
+
this.nav = inject(NavigationService);
|
|
50352
|
+
this.i18n = inject(I18nService);
|
|
50353
|
+
this.notifs = inject(NotificationsService);
|
|
50354
|
+
this.action = inject(NotificationActionService);
|
|
50355
|
+
this.toast = inject(ToastService);
|
|
50356
|
+
this.errors = inject(ValtechErrorService);
|
|
50357
|
+
this.auth = inject(AuthService);
|
|
50358
|
+
this.pageRefresh = inject(PageRefreshService);
|
|
50359
|
+
// `ActivatedRoute` opcional → la vista funciona embebida / en Storybook sin
|
|
50360
|
+
// router (solo lo usa para leer el route data de config como fallback). La
|
|
50361
|
+
// navegación va por `NavigationService` (wrapper opcional sobre `Router`).
|
|
50362
|
+
this.route = inject(ActivatedRoute, { optional: true });
|
|
50363
|
+
this.resolvedConfig = computed(() => {
|
|
50364
|
+
const fromRoute = (this.route?.snapshot.data['notificationsConfig'] ?? {});
|
|
50365
|
+
const merged = { ...fromRoute, ...(this.config ?? {}) };
|
|
50366
|
+
return {
|
|
50367
|
+
showPushOffCta: merged.showPushOffCta ?? true,
|
|
50368
|
+
enableMarkAll: merged.enableMarkAll ?? true,
|
|
50369
|
+
settingsNotificationsPath: merged.settingsNotificationsPath ?? DEFAULT_SETTINGS_PATH,
|
|
50370
|
+
i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
|
|
50371
|
+
pageSize: merged.pageSize ?? DEFAULT_PAGE_SIZE,
|
|
50372
|
+
maxLoad: merged.maxLoad ?? DEFAULT_MAX_LOAD,
|
|
50373
|
+
onNotificationOpen: merged.onNotificationOpen,
|
|
50374
|
+
};
|
|
50375
|
+
});
|
|
50376
|
+
this._expanded = signal(new Set());
|
|
50377
|
+
this._pageSize = signal(this.resolvedConfig().pageSize);
|
|
50378
|
+
this._bulkBusy = signal(false);
|
|
50379
|
+
this._loadingMore = signal(false);
|
|
50380
|
+
/**
|
|
50381
|
+
* Push activado = permiso del navegador en `granted`. Si NO está activado,
|
|
50382
|
+
* el user puede leer el inbox (Firestore) pero NO recibirá push en su
|
|
50383
|
+
* dispositivo → mostramos una card que lo invita a ir a configuración.
|
|
50384
|
+
* Se evalúa en cada montaje de la vista.
|
|
50385
|
+
*/
|
|
50386
|
+
this.pushEnabled = signal(this.auth.getNotificationPermissionState() === 'granted');
|
|
50387
|
+
/**
|
|
50388
|
+
* Stream firestore real-time vía el helper estándar `createRefreshableStream`
|
|
50389
|
+
* (modo real-time: factory devuelve un listener vivo `notifs.getAll(maxLoad)`).
|
|
50390
|
+
*
|
|
50391
|
+
* El helper gatea la primera suscripción por `firebaseAuthReady`, aplica un
|
|
50392
|
+
* `retry` corto con backoff y, tras agotar reintentos, emite el fallback (`[]`)
|
|
50393
|
+
* y marca `error` — estado de error en UI, no skeleton infinito. `reload()`
|
|
50394
|
+
* re-suscribe el stream completo (lo usa el pull-to-refresh).
|
|
50395
|
+
*/
|
|
50396
|
+
this._stream = createRefreshableStream(() => this.notifs.getAll(this.resolvedConfig().maxLoad), {
|
|
50397
|
+
fallback: [],
|
|
50398
|
+
// `notifs.getAll()` YA gatea internamente por `firebaseAuthReady`. Si el
|
|
50399
|
+
// stream también gateara, serían DOS esperas de auth apiladas.
|
|
50400
|
+
gateOnFirebaseAuth: false,
|
|
50401
|
+
// Cold start de PWA en iOS puede pasar de 8s en redes lentas; con 15s
|
|
50402
|
+
// evitamos el falso estado de error.
|
|
50403
|
+
firstEmitTimeoutMs: 15000,
|
|
50404
|
+
});
|
|
50405
|
+
/** True si el stream falló tras agotar reintentos → muestra estado de error. */
|
|
50406
|
+
this.streamError = this._stream.error;
|
|
50407
|
+
this.loading = computed(() => this._stream.data() === null);
|
|
50408
|
+
/**
|
|
50409
|
+
* Notifs reales mostradas. Filtramos docs sin título Y sin body — son ruido
|
|
50410
|
+
* (típicamente sentinel/system writes pre-schema con title/body opcionales).
|
|
50411
|
+
*/
|
|
50412
|
+
this.allNotifs = computed(() => (this._stream.data() ?? []).filter(n => !!(n.title && n.title.trim()) || !!(n.body && n.body.trim())));
|
|
50413
|
+
this.hasMore = computed(() => this.allNotifs().length > this._pageSize());
|
|
50414
|
+
this.showMarkAll = computed(() => this.resolvedConfig().enableMarkAll && !this.loading() && this.allNotifs().some(n => !n.isRead));
|
|
50415
|
+
/** Sub-conjunto paginado, agrupado por período. */
|
|
50416
|
+
this.groups = computed(() => {
|
|
50417
|
+
const items = this.allNotifs().slice(0, this._pageSize());
|
|
50418
|
+
const now = new Date();
|
|
50419
|
+
const buckets = {
|
|
50420
|
+
today: [],
|
|
50421
|
+
thisMonth: [],
|
|
50422
|
+
earlier: [],
|
|
50423
|
+
};
|
|
50424
|
+
for (const n of items) {
|
|
50425
|
+
buckets[this.bucketOf(n, now)].push(n);
|
|
50426
|
+
}
|
|
50427
|
+
const out = [];
|
|
50428
|
+
if (buckets.today.length) {
|
|
50429
|
+
out.push({ key: 'today', label: this.tt('groupToday'), items: buckets.today });
|
|
50430
|
+
}
|
|
50431
|
+
if (buckets.thisMonth.length) {
|
|
50432
|
+
out.push({
|
|
50433
|
+
key: 'thisMonth',
|
|
50434
|
+
label: this.tt('groupThisMonth'),
|
|
50435
|
+
items: buckets.thisMonth,
|
|
50436
|
+
});
|
|
50437
|
+
}
|
|
50438
|
+
if (buckets.earlier.length) {
|
|
50439
|
+
out.push({ key: 'earlier', label: this.tt('groupEarlier'), items: buckets.earlier });
|
|
50440
|
+
}
|
|
50441
|
+
return out;
|
|
50442
|
+
});
|
|
50443
|
+
this.pageTitle = computed(() => this.tt('pageTitle'));
|
|
50444
|
+
this.pageDescription = computed(() => this.tt('pageDescription'));
|
|
50445
|
+
// CTA "activa las notificaciones push" — visible cuando pushEnabled() es false.
|
|
50446
|
+
this.ctaTitle = computed(() => this.tt('pushOffTitle'));
|
|
50447
|
+
this.ctaHint = computed(() => this.tt('pushOffHint'));
|
|
50448
|
+
this.ctaButtonProps = computed(() => ({
|
|
50449
|
+
text: this.tt('pushOffCta'),
|
|
50450
|
+
color: 'dark',
|
|
50451
|
+
fill: 'outline',
|
|
50452
|
+
size: 'small',
|
|
50453
|
+
shape: 'round',
|
|
50454
|
+
type: 'button',
|
|
50455
|
+
}));
|
|
50456
|
+
/**
|
|
50457
|
+
* Estado de error de carga: convierte el error del stream en
|
|
50458
|
+
* `EmptyStateMetadata` via el helper canónico `createErrorStateProps`.
|
|
50459
|
+
* El helper decide variante (`offline` vs `error`) por `isNetwork`.
|
|
50460
|
+
* Null cuando el stream no ha fallado.
|
|
50461
|
+
*/
|
|
50462
|
+
this.errorState = computed(() => {
|
|
50463
|
+
this.i18n.lang();
|
|
50464
|
+
const err = this.streamError();
|
|
50465
|
+
if (!err)
|
|
50466
|
+
return null;
|
|
50467
|
+
return createErrorStateProps(err, {
|
|
50468
|
+
title: {
|
|
50469
|
+
offline: this.tt('offlineTitle'),
|
|
50470
|
+
error: this.tt('loadErrorTitle'),
|
|
50471
|
+
},
|
|
50472
|
+
description: {
|
|
50473
|
+
offline: this.tt('offlineHint'),
|
|
50474
|
+
error: interpretError(err).message || this.tt('loadErrorHint'),
|
|
50475
|
+
},
|
|
50476
|
+
retryLabel: this.tt('retryCta'),
|
|
50477
|
+
onRetry: () => this._stream.reload(),
|
|
50478
|
+
retrying: this.loading(),
|
|
50479
|
+
});
|
|
50480
|
+
});
|
|
50481
|
+
/**
|
|
50482
|
+
* Estado vacío (lista real = 0 notificaciones, sin error de carga).
|
|
50483
|
+
* Se construye manualmente con `variant: 'empty'` — sin helper de error.
|
|
50484
|
+
*/
|
|
50485
|
+
this.emptyStateProps = computed(() => {
|
|
50486
|
+
this.i18n.lang();
|
|
50487
|
+
return {
|
|
50488
|
+
variant: 'empty',
|
|
50489
|
+
icon: 'notifications-off-outline',
|
|
50490
|
+
title: this.tt('empty'),
|
|
50491
|
+
description: this.tt('emptyHint'),
|
|
50492
|
+
action: {
|
|
50493
|
+
label: this.tt('emptyCta'),
|
|
50494
|
+
handler: () => this.onGoToSettings(),
|
|
50495
|
+
},
|
|
50496
|
+
};
|
|
50497
|
+
});
|
|
50498
|
+
this.seeMore = computed(() => this.tt('seeMore'));
|
|
50499
|
+
this.seeLess = computed(() => this.tt('seeLess'));
|
|
50500
|
+
this.markAllButtonProps = computed(() => ({
|
|
50501
|
+
text: this.tt('markAll'),
|
|
50502
|
+
color: 'dark',
|
|
50503
|
+
fill: 'outline',
|
|
50504
|
+
size: 'small',
|
|
50505
|
+
shape: 'round',
|
|
50506
|
+
type: 'button',
|
|
50507
|
+
icon: { name: 'checkmark-done-outline', slot: 'start' },
|
|
50508
|
+
state: this._bulkBusy() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
50509
|
+
}));
|
|
50510
|
+
this.loadMoreButtonProps = computed(() => ({
|
|
50511
|
+
text: this.tt('loadMore'),
|
|
50512
|
+
color: 'dark',
|
|
50513
|
+
fill: 'clear',
|
|
50514
|
+
size: 'default',
|
|
50515
|
+
shape: 'round',
|
|
50516
|
+
type: 'button',
|
|
50517
|
+
state: this._loadingMore() ? ComponentStates.WORKING : ComponentStates.ENABLED,
|
|
50518
|
+
}));
|
|
50519
|
+
const ns = this.ns;
|
|
50520
|
+
if (!this.i18n.hasNamespace(ns)) {
|
|
50521
|
+
this.i18n.registerContent(ns, NOTIFICATIONS_VIEW_I18N);
|
|
50522
|
+
}
|
|
50523
|
+
this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
|
|
50524
|
+
// Re-aplicar header en cambio de idioma.
|
|
50525
|
+
effect(() => {
|
|
50526
|
+
this.i18n.lang();
|
|
50527
|
+
this.nav.setBackHeader('pageTitle', this.ns, { withMenu: true });
|
|
50528
|
+
}, { allowSignalWrites: true });
|
|
50529
|
+
}
|
|
50530
|
+
/**
|
|
50531
|
+
* Registra el pull-to-refresh estándar. Usamos `ngOnInit`/`ngOnDestroy`
|
|
50532
|
+
* (NO `ionViewWillEnter`) porque `val-page-wrapper` usa `<router-outlet>`
|
|
50533
|
+
* plano — los hooks `ionView*` solo disparan dentro de `<ion-router-outlet>`.
|
|
50534
|
+
*/
|
|
50535
|
+
ngOnInit() {
|
|
50536
|
+
this.pageRefresh.register(() => this.reload());
|
|
50537
|
+
}
|
|
50538
|
+
/** Quita el handler al destruir la vista — el refresher desaparece. */
|
|
50539
|
+
ngOnDestroy() {
|
|
50540
|
+
this.pageRefresh.unregister();
|
|
50541
|
+
}
|
|
50542
|
+
/**
|
|
50543
|
+
* Refresh manual: re-suscribe el stream Firestore (listener nuevo) vía el
|
|
50544
|
+
* helper. Resuelve tras un margen breve — el stream es real-time y re-emite
|
|
50545
|
+
* en milisegundos; el delay sólo mantiene el spinner visible lo justo.
|
|
50546
|
+
*/
|
|
50547
|
+
async reload() {
|
|
50548
|
+
this._stream.reload();
|
|
50549
|
+
await new Promise(r => setTimeout(r, 700));
|
|
50550
|
+
}
|
|
50551
|
+
// ---------------------------------------------------------------------------
|
|
50552
|
+
// Per-item helpers (puros — usables desde template).
|
|
50553
|
+
// ---------------------------------------------------------------------------
|
|
50554
|
+
openButtonProps() {
|
|
50555
|
+
return {
|
|
50556
|
+
text: this.tt('open'),
|
|
50557
|
+
color: 'dark',
|
|
50558
|
+
fill: 'clear',
|
|
50559
|
+
size: 'small',
|
|
50560
|
+
shape: 'round',
|
|
50561
|
+
type: 'button',
|
|
50562
|
+
icon: { name: 'open-outline', slot: 'end' },
|
|
50563
|
+
};
|
|
50564
|
+
}
|
|
50565
|
+
isExpanded(id) {
|
|
50566
|
+
return this._expanded().has(id);
|
|
50567
|
+
}
|
|
50568
|
+
toggleExpand(id) {
|
|
50569
|
+
const next = new Set(this._expanded());
|
|
50570
|
+
if (next.has(id))
|
|
50571
|
+
next.delete(id);
|
|
50572
|
+
else
|
|
50573
|
+
next.add(id);
|
|
50574
|
+
this._expanded.set(next);
|
|
50575
|
+
}
|
|
50576
|
+
isLong(body) {
|
|
50577
|
+
if (!body)
|
|
50578
|
+
return false;
|
|
50579
|
+
return body.length > 110 || body.includes('\n');
|
|
50580
|
+
}
|
|
50581
|
+
/**
|
|
50582
|
+
* Icon resolver con orden de fallback:
|
|
50583
|
+
* 1. notif.payload.icon (forward-compat, sin breaking change en schema)
|
|
50584
|
+
* 2. mapa por notif.type
|
|
50585
|
+
* 3. default notifications-outline
|
|
50586
|
+
*/
|
|
50587
|
+
resolveIcon(n) {
|
|
50588
|
+
const hint = n.payload?.['icon'];
|
|
50589
|
+
if (hint?.value) {
|
|
50590
|
+
if (hint.type === 'image' || hint.type === 'avatar') {
|
|
50591
|
+
return { kind: hint.type, src: hint.value };
|
|
50592
|
+
}
|
|
50593
|
+
// Ignoramos hint.color — todos los iconos van en dark por convención UX.
|
|
50594
|
+
return { kind: 'ionicon', name: hint.value, color: 'dark' };
|
|
50595
|
+
}
|
|
50596
|
+
const t = n.type;
|
|
50597
|
+
const name = (() => {
|
|
50598
|
+
switch (t) {
|
|
50599
|
+
case 'approval':
|
|
50600
|
+
return 'checkmark-circle-outline';
|
|
50601
|
+
case 'chat':
|
|
50602
|
+
case 'chatbot':
|
|
50603
|
+
return 'chatbubble-outline';
|
|
50604
|
+
case 'comment':
|
|
50605
|
+
return 'chatbox-ellipses-outline';
|
|
50606
|
+
case 'reminder':
|
|
50607
|
+
return 'alarm-outline';
|
|
50608
|
+
case 'system':
|
|
50609
|
+
return 'cog-outline';
|
|
50610
|
+
case 'user':
|
|
50611
|
+
case 'mention':
|
|
50612
|
+
return 'person-outline';
|
|
50613
|
+
case 'document':
|
|
50614
|
+
return 'document-text-outline';
|
|
50615
|
+
default:
|
|
50616
|
+
return 'notifications-outline';
|
|
50617
|
+
}
|
|
50618
|
+
})();
|
|
50619
|
+
return { kind: 'ionicon', name, color: 'dark' };
|
|
50620
|
+
}
|
|
50621
|
+
ioniconName(n) {
|
|
50622
|
+
const r = this.resolveIcon(n);
|
|
50623
|
+
return r.kind === 'ionicon' ? r.name : '';
|
|
50624
|
+
}
|
|
50625
|
+
ioniconColor(n) {
|
|
50626
|
+
const r = this.resolveIcon(n);
|
|
50627
|
+
return r.kind === 'ionicon' ? r.color : undefined;
|
|
50628
|
+
}
|
|
50629
|
+
imageSrc(n) {
|
|
50630
|
+
const r = this.resolveIcon(n);
|
|
50631
|
+
return r.kind === 'image' || r.kind === 'avatar' ? r.src : '';
|
|
50632
|
+
}
|
|
50633
|
+
ionColor(token) {
|
|
50634
|
+
if (!token)
|
|
50635
|
+
return 'var(--ion-color-primary)';
|
|
50636
|
+
return `var(--ion-color-${token})`;
|
|
50637
|
+
}
|
|
50638
|
+
relativeTime(n) {
|
|
50639
|
+
const d = n.createdAt instanceof Date ? n.createdAt : null;
|
|
50640
|
+
if (!d)
|
|
50641
|
+
return '';
|
|
50642
|
+
const diffMs = Date.now() - d.getTime();
|
|
50643
|
+
const min = Math.floor(diffMs / 60000);
|
|
50644
|
+
if (min < 1)
|
|
50645
|
+
return this.tt('timeNow');
|
|
50646
|
+
if (min < 60)
|
|
50647
|
+
return this.tt('timeMinutes').replace('{n}', String(min));
|
|
50648
|
+
const h = Math.floor(min / 60);
|
|
50649
|
+
if (h < 24)
|
|
50650
|
+
return this.tt('timeHours').replace('{n}', String(h));
|
|
50651
|
+
const days = Math.floor(h / 24);
|
|
50652
|
+
return this.tt('timeDays').replace('{n}', String(days));
|
|
50653
|
+
}
|
|
50654
|
+
/** Devuelve label "Desde {appName}" si la notif viene de otra app, sino null. */
|
|
50655
|
+
showCrossAppBadge(n) {
|
|
50656
|
+
const currentApp = 'showcase';
|
|
50657
|
+
if (!n.appId || n.appId === currentApp)
|
|
50658
|
+
return null;
|
|
50659
|
+
return this.tt('crossApp').replace('{app}', n.appId);
|
|
50660
|
+
}
|
|
50661
|
+
// ---------------------------------------------------------------------------
|
|
50662
|
+
// Actions
|
|
50663
|
+
// ---------------------------------------------------------------------------
|
|
50664
|
+
async onOpen(n) {
|
|
50665
|
+
try {
|
|
50666
|
+
const result = await this.action.open(n);
|
|
50667
|
+
this.resolvedConfig().onNotificationOpen?.(n, result);
|
|
50668
|
+
}
|
|
50669
|
+
catch (err) {
|
|
50670
|
+
this.errors.handle(err, {
|
|
50671
|
+
context: 'notifications.open',
|
|
50672
|
+
fallbackKey: 'openError',
|
|
50673
|
+
i18nNamespace: this.ns,
|
|
50674
|
+
});
|
|
50675
|
+
}
|
|
50676
|
+
}
|
|
50677
|
+
async onMarkAll() {
|
|
50678
|
+
if (this._bulkBusy())
|
|
50679
|
+
return;
|
|
50680
|
+
this._bulkBusy.set(true);
|
|
50681
|
+
try {
|
|
50682
|
+
await this.notifs.markAllAsRead();
|
|
50683
|
+
this.toast.show({
|
|
50684
|
+
message: this.tt('markedAllSuccess'),
|
|
50685
|
+
color: 'dark',
|
|
50686
|
+
duration: 2500,
|
|
50687
|
+
position: 'top',
|
|
50688
|
+
});
|
|
50689
|
+
}
|
|
50690
|
+
catch (err) {
|
|
50691
|
+
this.errors.handle(err, {
|
|
50692
|
+
context: 'notifications.markAll',
|
|
50693
|
+
fallbackKey: 'markedAllError',
|
|
50694
|
+
i18nNamespace: this.ns,
|
|
50695
|
+
});
|
|
50696
|
+
}
|
|
50697
|
+
finally {
|
|
50698
|
+
this._bulkBusy.set(false);
|
|
50699
|
+
}
|
|
50700
|
+
}
|
|
50701
|
+
onLoadMore() {
|
|
50702
|
+
if (this._loadingMore())
|
|
50703
|
+
return;
|
|
50704
|
+
this._loadingMore.set(true);
|
|
50705
|
+
// Slice viene del cache local — no hay fetch real, pero un breve spinner
|
|
50706
|
+
// confirma al user que el click registró y suaviza la aparición de items.
|
|
50707
|
+
setTimeout(() => {
|
|
50708
|
+
this._pageSize.update(n => n + this.pageStep);
|
|
50709
|
+
this._loadingMore.set(false);
|
|
50710
|
+
}, 250);
|
|
50711
|
+
}
|
|
50712
|
+
onGoToSettings() {
|
|
50713
|
+
this.nav.navigateByUrl(this.resolvedConfig().settingsNotificationsPath);
|
|
50714
|
+
}
|
|
50715
|
+
// ---------------------------------------------------------------------------
|
|
50716
|
+
// Grouping helpers
|
|
50717
|
+
// ---------------------------------------------------------------------------
|
|
50718
|
+
bucketOf(n, now) {
|
|
50719
|
+
const d = n.createdAt instanceof Date ? n.createdAt : null;
|
|
50720
|
+
if (!d)
|
|
50721
|
+
return 'earlier';
|
|
50722
|
+
if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate()) {
|
|
50723
|
+
return 'today';
|
|
50724
|
+
}
|
|
50725
|
+
if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth()) {
|
|
50726
|
+
return 'thisMonth';
|
|
50727
|
+
}
|
|
50728
|
+
return 'earlier';
|
|
50729
|
+
}
|
|
50730
|
+
tt(key) {
|
|
50731
|
+
return this.i18n.t(key, this.ns);
|
|
50732
|
+
}
|
|
50733
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationsViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
50734
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: NotificationsViewComponent, isStandalone: true, selector: "val-notifications-view", inputs: { config: "config" }, ngImport: i0, template: `
|
|
50735
|
+
<div class="page">
|
|
50736
|
+
<header class="page-header">
|
|
50737
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
50738
|
+
<val-title
|
|
50739
|
+
[props]="{
|
|
50740
|
+
size: 'large',
|
|
50741
|
+
color: 'dark',
|
|
50742
|
+
bold: false,
|
|
50743
|
+
content: pageDescription(),
|
|
50744
|
+
}"
|
|
50745
|
+
/>
|
|
50746
|
+
@if (showMarkAll()) {
|
|
50747
|
+
<div class="row-actions">
|
|
50748
|
+
<val-button [props]="markAllButtonProps()" (click)="onMarkAll()" />
|
|
50749
|
+
</div>
|
|
50750
|
+
}
|
|
50751
|
+
</header>
|
|
50752
|
+
|
|
50753
|
+
@if (resolvedConfig().showPushOffCta && !pushEnabled()) {
|
|
50754
|
+
<section class="notif-cta">
|
|
50755
|
+
<ion-icon class="notif-cta__icon" name="notifications-off-outline" aria-hidden="true" />
|
|
50756
|
+
<div class="notif-cta__body">
|
|
50757
|
+
<val-text
|
|
50758
|
+
[props]="{
|
|
50759
|
+
content: ctaTitle(),
|
|
50760
|
+
size: 'medium',
|
|
50761
|
+
color: 'dark',
|
|
50762
|
+
bold: true,
|
|
50763
|
+
}"
|
|
50764
|
+
/>
|
|
50765
|
+
<val-text
|
|
50766
|
+
[props]="{
|
|
50767
|
+
content: ctaHint(),
|
|
50768
|
+
size: 'small',
|
|
50769
|
+
color: 'medium',
|
|
50770
|
+
bold: false,
|
|
50771
|
+
}"
|
|
50772
|
+
/>
|
|
50773
|
+
<div class="row-actions">
|
|
50774
|
+
<val-button [props]="ctaButtonProps()" (click)="onGoToSettings()" />
|
|
50775
|
+
</div>
|
|
50776
|
+
</div>
|
|
50777
|
+
</section>
|
|
50778
|
+
}
|
|
50779
|
+
|
|
50780
|
+
@if (loading()) {
|
|
50781
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 6 }" />
|
|
50782
|
+
} @else if (errorState()) {
|
|
50783
|
+
<val-empty-state [props]="errorState()!" />
|
|
50784
|
+
} @else if (allNotifs().length === 0) {
|
|
50785
|
+
<val-empty-state [props]="emptyStateProps()" />
|
|
50786
|
+
} @else {
|
|
50787
|
+
@for (group of groups(); track group.key) {
|
|
50788
|
+
<section class="group">
|
|
50789
|
+
<val-title
|
|
50790
|
+
[props]="{
|
|
50791
|
+
size: 'medium',
|
|
50792
|
+
color: 'dark',
|
|
50793
|
+
bold: true,
|
|
50794
|
+
content: group.label,
|
|
50795
|
+
}"
|
|
50796
|
+
/>
|
|
50797
|
+
<ul class="notif-list">
|
|
50798
|
+
@for (n of group.items; track n.id) {
|
|
50799
|
+
<li class="notif" [class.notif--unread]="!n.isRead">
|
|
50800
|
+
<div class="notif__icon">
|
|
50801
|
+
@switch (resolveIcon(n).kind) {
|
|
50802
|
+
@case ('ionicon') {
|
|
50803
|
+
<ion-icon
|
|
50804
|
+
[name]="ioniconName(n)"
|
|
50805
|
+
[style.color]="ionColor(ioniconColor(n))"
|
|
50806
|
+
aria-hidden="true"
|
|
50807
|
+
/>
|
|
50808
|
+
}
|
|
50809
|
+
@case ('image') {
|
|
50810
|
+
<img [src]="imageSrc(n)" alt="" loading="lazy" />
|
|
50811
|
+
}
|
|
50812
|
+
@case ('avatar') {
|
|
50813
|
+
<img class="notif__avatar" [src]="imageSrc(n)" alt="" loading="lazy" />
|
|
50814
|
+
}
|
|
50815
|
+
}
|
|
50816
|
+
</div>
|
|
50817
|
+
|
|
50818
|
+
<div class="notif__body">
|
|
50819
|
+
<div class="notif__head">
|
|
50820
|
+
<val-text
|
|
50821
|
+
[props]="{
|
|
50822
|
+
content: n.title || '—',
|
|
50823
|
+
size: 'medium',
|
|
50824
|
+
color: 'dark',
|
|
50825
|
+
bold: true,
|
|
50826
|
+
}"
|
|
50827
|
+
/>
|
|
50828
|
+
<span class="notif__time">{{ relativeTime(n) }}</span>
|
|
50829
|
+
</div>
|
|
50830
|
+
@if (n.body) {
|
|
50831
|
+
<p class="notif__text" [class.notif__text--clamped]="!isExpanded(n.id) && isLong(n.body)">
|
|
50832
|
+
{{ n.body }}
|
|
50833
|
+
</p>
|
|
50834
|
+
@if (isLong(n.body)) {
|
|
50835
|
+
<button class="notif__expand" type="button" (click)="toggleExpand(n.id)">
|
|
50836
|
+
{{ isExpanded(n.id) ? seeLess() : seeMore() }}
|
|
50837
|
+
</button>
|
|
50838
|
+
}
|
|
50839
|
+
}
|
|
50840
|
+
@if (showCrossAppBadge(n); as appName) {
|
|
50841
|
+
<span class="notif__app">{{ appName }}</span>
|
|
50842
|
+
}
|
|
50843
|
+
@if (n.actionRoute) {
|
|
50844
|
+
<div class="row-actions row-actions--end">
|
|
50845
|
+
<val-button [props]="openButtonProps()" (click)="onOpen(n)" />
|
|
50846
|
+
</div>
|
|
50847
|
+
}
|
|
50848
|
+
</div>
|
|
50849
|
+
</li>
|
|
50850
|
+
}
|
|
50851
|
+
</ul>
|
|
50852
|
+
</section>
|
|
50853
|
+
}
|
|
50854
|
+
|
|
50855
|
+
@if (hasMore()) {
|
|
50856
|
+
<div class="row-actions row-actions--center">
|
|
50857
|
+
<val-button [props]="loadMoreButtonProps()" (click)="onLoadMore()" />
|
|
50858
|
+
</div>
|
|
50859
|
+
}
|
|
50860
|
+
}
|
|
50861
|
+
</div>
|
|
50862
|
+
`, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.row-actions{margin-top:12px}.row-actions--center{display:flex;justify-content:center;margin-top:20px}.row-actions--end{display:flex;justify-content:flex-end;margin-top:8px}.notif-cta{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;margin-bottom:16px;border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));border-radius:14px;background:var(--ion-color-light)}.notif-cta__icon{font-size:24px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0;margin-top:2px}.notif-cta__body{display:flex;flex-direction:column;gap:2px}.group{padding:12px 0}.group+.group{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.notif-list{list-style:none;padding:0;margin:12px 0 0;display:flex;flex-direction:column;gap:10px}.notif{display:grid;grid-template-columns:44px 1fr;gap:12px;padding:12px;border-radius:12px;background:transparent;transition:background .12s ease}.notif--unread{background:var(--ion-color-light, rgba(0, 0, 0, .04))}.notif__icon{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--ion-color-light, rgba(0, 0, 0, .06));overflow:hidden}.notif__icon ion-icon{font-size:22px}.notif__icon img{width:100%;height:100%;object-fit:cover}.notif__avatar{border-radius:50%}.notif__body{display:flex;flex-direction:column;gap:4px;min-width:0}.notif__head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.notif__time{font-size:12px;color:var(--ion-color-medium, #92949c);flex-shrink:0}.notif__text{margin:0;font-size:14px;line-height:1.4;color:var(--ion-color-dark, #1a1a1a);white-space:pre-wrap}.notif__text--clamped{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notif__expand{background:none;border:0;padding:0;font-size:13px;color:var(--ion-color-dark, #1a1a1a);cursor:pointer;align-self:flex-start;text-decoration:underline}.notif__app{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--ion-color-medium, #92949c);margin-top:2px}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }] }); }
|
|
50863
|
+
}
|
|
50864
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationsViewComponent, decorators: [{
|
|
50865
|
+
type: Component,
|
|
50866
|
+
args: [{ selector: 'val-notifications-view', standalone: true, imports: [
|
|
50867
|
+
IonIcon,
|
|
50868
|
+
DisplayComponent,
|
|
50869
|
+
TitleComponent,
|
|
50870
|
+
TextComponent,
|
|
50871
|
+
ButtonComponent,
|
|
50872
|
+
SkeletonLayoutComponent,
|
|
50873
|
+
EmptyStateComponent,
|
|
50874
|
+
], template: `
|
|
50875
|
+
<div class="page">
|
|
50876
|
+
<header class="page-header">
|
|
50877
|
+
<val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
|
|
50878
|
+
<val-title
|
|
50879
|
+
[props]="{
|
|
50880
|
+
size: 'large',
|
|
50881
|
+
color: 'dark',
|
|
50882
|
+
bold: false,
|
|
50883
|
+
content: pageDescription(),
|
|
50884
|
+
}"
|
|
50885
|
+
/>
|
|
50886
|
+
@if (showMarkAll()) {
|
|
50887
|
+
<div class="row-actions">
|
|
50888
|
+
<val-button [props]="markAllButtonProps()" (click)="onMarkAll()" />
|
|
50889
|
+
</div>
|
|
50890
|
+
}
|
|
50891
|
+
</header>
|
|
50892
|
+
|
|
50893
|
+
@if (resolvedConfig().showPushOffCta && !pushEnabled()) {
|
|
50894
|
+
<section class="notif-cta">
|
|
50895
|
+
<ion-icon class="notif-cta__icon" name="notifications-off-outline" aria-hidden="true" />
|
|
50896
|
+
<div class="notif-cta__body">
|
|
50897
|
+
<val-text
|
|
50898
|
+
[props]="{
|
|
50899
|
+
content: ctaTitle(),
|
|
50900
|
+
size: 'medium',
|
|
50901
|
+
color: 'dark',
|
|
50902
|
+
bold: true,
|
|
50903
|
+
}"
|
|
50904
|
+
/>
|
|
50905
|
+
<val-text
|
|
50906
|
+
[props]="{
|
|
50907
|
+
content: ctaHint(),
|
|
50908
|
+
size: 'small',
|
|
50909
|
+
color: 'medium',
|
|
50910
|
+
bold: false,
|
|
50911
|
+
}"
|
|
50912
|
+
/>
|
|
50913
|
+
<div class="row-actions">
|
|
50914
|
+
<val-button [props]="ctaButtonProps()" (click)="onGoToSettings()" />
|
|
50915
|
+
</div>
|
|
50916
|
+
</div>
|
|
50917
|
+
</section>
|
|
50918
|
+
}
|
|
50919
|
+
|
|
50920
|
+
@if (loading()) {
|
|
50921
|
+
<val-skeleton-layout [props]="{ preset: 'list', rows: 6 }" />
|
|
50922
|
+
} @else if (errorState()) {
|
|
50923
|
+
<val-empty-state [props]="errorState()!" />
|
|
50924
|
+
} @else if (allNotifs().length === 0) {
|
|
50925
|
+
<val-empty-state [props]="emptyStateProps()" />
|
|
50926
|
+
} @else {
|
|
50927
|
+
@for (group of groups(); track group.key) {
|
|
50928
|
+
<section class="group">
|
|
50929
|
+
<val-title
|
|
50930
|
+
[props]="{
|
|
50931
|
+
size: 'medium',
|
|
50932
|
+
color: 'dark',
|
|
50933
|
+
bold: true,
|
|
50934
|
+
content: group.label,
|
|
50935
|
+
}"
|
|
50936
|
+
/>
|
|
50937
|
+
<ul class="notif-list">
|
|
50938
|
+
@for (n of group.items; track n.id) {
|
|
50939
|
+
<li class="notif" [class.notif--unread]="!n.isRead">
|
|
50940
|
+
<div class="notif__icon">
|
|
50941
|
+
@switch (resolveIcon(n).kind) {
|
|
50942
|
+
@case ('ionicon') {
|
|
50943
|
+
<ion-icon
|
|
50944
|
+
[name]="ioniconName(n)"
|
|
50945
|
+
[style.color]="ionColor(ioniconColor(n))"
|
|
50946
|
+
aria-hidden="true"
|
|
50947
|
+
/>
|
|
50948
|
+
}
|
|
50949
|
+
@case ('image') {
|
|
50950
|
+
<img [src]="imageSrc(n)" alt="" loading="lazy" />
|
|
50951
|
+
}
|
|
50952
|
+
@case ('avatar') {
|
|
50953
|
+
<img class="notif__avatar" [src]="imageSrc(n)" alt="" loading="lazy" />
|
|
50954
|
+
}
|
|
50955
|
+
}
|
|
50956
|
+
</div>
|
|
50957
|
+
|
|
50958
|
+
<div class="notif__body">
|
|
50959
|
+
<div class="notif__head">
|
|
50960
|
+
<val-text
|
|
50961
|
+
[props]="{
|
|
50962
|
+
content: n.title || '—',
|
|
50963
|
+
size: 'medium',
|
|
50964
|
+
color: 'dark',
|
|
50965
|
+
bold: true,
|
|
50966
|
+
}"
|
|
50967
|
+
/>
|
|
50968
|
+
<span class="notif__time">{{ relativeTime(n) }}</span>
|
|
50969
|
+
</div>
|
|
50970
|
+
@if (n.body) {
|
|
50971
|
+
<p class="notif__text" [class.notif__text--clamped]="!isExpanded(n.id) && isLong(n.body)">
|
|
50972
|
+
{{ n.body }}
|
|
50973
|
+
</p>
|
|
50974
|
+
@if (isLong(n.body)) {
|
|
50975
|
+
<button class="notif__expand" type="button" (click)="toggleExpand(n.id)">
|
|
50976
|
+
{{ isExpanded(n.id) ? seeLess() : seeMore() }}
|
|
50977
|
+
</button>
|
|
50978
|
+
}
|
|
50979
|
+
}
|
|
50980
|
+
@if (showCrossAppBadge(n); as appName) {
|
|
50981
|
+
<span class="notif__app">{{ appName }}</span>
|
|
50982
|
+
}
|
|
50983
|
+
@if (n.actionRoute) {
|
|
50984
|
+
<div class="row-actions row-actions--end">
|
|
50985
|
+
<val-button [props]="openButtonProps()" (click)="onOpen(n)" />
|
|
50986
|
+
</div>
|
|
50987
|
+
}
|
|
50988
|
+
</div>
|
|
50989
|
+
</li>
|
|
50990
|
+
}
|
|
50991
|
+
</ul>
|
|
50992
|
+
</section>
|
|
50993
|
+
}
|
|
50994
|
+
|
|
50995
|
+
@if (hasMore()) {
|
|
50996
|
+
<div class="row-actions row-actions--center">
|
|
50997
|
+
<val-button [props]="loadMoreButtonProps()" (click)="onLoadMore()" />
|
|
50998
|
+
</div>
|
|
50999
|
+
}
|
|
51000
|
+
}
|
|
51001
|
+
</div>
|
|
51002
|
+
`, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.row-actions{margin-top:12px}.row-actions--center{display:flex;justify-content:center;margin-top:20px}.row-actions--end{display:flex;justify-content:flex-end;margin-top:8px}.notif-cta{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;margin-bottom:16px;border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));border-radius:14px;background:var(--ion-color-light)}.notif-cta__icon{font-size:24px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0;margin-top:2px}.notif-cta__body{display:flex;flex-direction:column;gap:2px}.group{padding:12px 0}.group+.group{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.notif-list{list-style:none;padding:0;margin:12px 0 0;display:flex;flex-direction:column;gap:10px}.notif{display:grid;grid-template-columns:44px 1fr;gap:12px;padding:12px;border-radius:12px;background:transparent;transition:background .12s ease}.notif--unread{background:var(--ion-color-light, rgba(0, 0, 0, .04))}.notif__icon{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--ion-color-light, rgba(0, 0, 0, .06));overflow:hidden}.notif__icon ion-icon{font-size:22px}.notif__icon img{width:100%;height:100%;object-fit:cover}.notif__avatar{border-radius:50%}.notif__body{display:flex;flex-direction:column;gap:4px;min-width:0}.notif__head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.notif__time{font-size:12px;color:var(--ion-color-medium, #92949c);flex-shrink:0}.notif__text{margin:0;font-size:14px;line-height:1.4;color:var(--ion-color-dark, #1a1a1a);white-space:pre-wrap}.notif__text--clamped{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notif__expand{background:none;border:0;padding:0;font-size:13px;color:var(--ion-color-dark, #1a1a1a);cursor:pointer;align-self:flex-start;text-decoration:underline}.notif__app{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--ion-color-medium, #92949c);margin-top:2px}\n"] }]
|
|
51003
|
+
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
51004
|
+
type: Input
|
|
51005
|
+
}] } });
|
|
51006
|
+
|
|
51007
|
+
/**
|
|
51008
|
+
* Helper para montar el inbox de notificaciones (`val-notifications-view`) como
|
|
51009
|
+
* ruta en una app del factory. El `config` se pasa por route `data`
|
|
51010
|
+
* (`notificationsConfig`) y el componente lo lee como fallback de su
|
|
51011
|
+
* `@Input() config`.
|
|
51012
|
+
*
|
|
51013
|
+
* @example
|
|
51014
|
+
* ```ts
|
|
51015
|
+
* // app.routes.ts
|
|
51016
|
+
* export const appRoutes: Routes = [
|
|
51017
|
+
* ...provideValtechNotificationsRoutes(), // → /app/notifications
|
|
51018
|
+
* { path: 'settings', loadChildren: () => ... },
|
|
51019
|
+
* ];
|
|
51020
|
+
*
|
|
51021
|
+
* // con config acotada:
|
|
51022
|
+
* ...provideValtechNotificationsRoutes({ config: { enableMarkAll: false } }),
|
|
51023
|
+
* ...provideValtechNotificationsRoutes({
|
|
51024
|
+
* config: { settingsNotificationsPath: '/app/preferences/push' },
|
|
51025
|
+
* }),
|
|
51026
|
+
* ```
|
|
51027
|
+
*/
|
|
51028
|
+
function provideValtechNotificationsRoutes(opts) {
|
|
51029
|
+
return [
|
|
51030
|
+
{
|
|
51031
|
+
path: opts?.path ?? 'notifications',
|
|
51032
|
+
component: NotificationsViewComponent,
|
|
51033
|
+
data: { notificationsConfig: opts?.config },
|
|
51034
|
+
},
|
|
51035
|
+
];
|
|
51036
|
+
}
|
|
51037
|
+
|
|
50111
51038
|
/** Built-in label sets for the three platform locales. */
|
|
50112
51039
|
const CALLOUT_LABELS = {
|
|
50113
51040
|
es: {
|
|
@@ -52150,124 +53077,6 @@ function beautifyLegalArticle(article) {
|
|
|
52150
53077
|
* Cliente NUNCA escribe directo — todas las mutaciones via PUT /v2/auth/preferences.
|
|
52151
53078
|
*/
|
|
52152
53079
|
|
|
52153
|
-
/**
|
|
52154
|
-
* Refreshable Stream
|
|
52155
|
-
*
|
|
52156
|
-
* Helper estándar para vistas que consumen datos de Firestore. Soporta DOS
|
|
52157
|
-
* modos — el caller decide cuál vía la `factory`:
|
|
52158
|
-
*
|
|
52159
|
-
* - **one-shot** (recomendado por defecto): `() => from(svc.getAllOnce())`.
|
|
52160
|
-
* Carga una vez y completa. Más barato — sin listener vivo.
|
|
52161
|
-
* - **real-time**: `() => svc.getAll()`. Listener Firestore que auto-actualiza.
|
|
52162
|
-
* Solo donde importa (inbox de notificaciones, badges).
|
|
52163
|
-
*
|
|
52164
|
-
* En ambos modos la primera suscripción se gatea por `firebaseAuthReady`
|
|
52165
|
-
* (sesión de Firebase Auth lista) — así el listener/lectura nunca se adjunta
|
|
52166
|
-
* antes de que `request.auth` esté disponible en las reglas de Firestore, lo
|
|
52167
|
-
* que cierra la ventana de `permission-denied` en cold start de PWA.
|
|
52168
|
-
*
|
|
52169
|
-
* El `retry` con backoff corto es solo red de seguridad para reconexiones
|
|
52170
|
-
* transitorias (no para cubrir cold start — eso lo cubre el gate de auth).
|
|
52171
|
-
*/
|
|
52172
|
-
/**
|
|
52173
|
-
* Crea un stream refrescable a partir de una factory de Observable.
|
|
52174
|
-
*
|
|
52175
|
-
* DEBE llamarse en un injection context (field initializer o constructor) —
|
|
52176
|
-
* usa `toSignal`/`toObservable`/`inject` internamente.
|
|
52177
|
-
*
|
|
52178
|
-
* @param factory - Función que produce el Observable a consumir. El caller
|
|
52179
|
-
* decide el modo: `() => from(svc.getAllOnce())` (one-shot) o
|
|
52180
|
-
* `() => svc.getAll()` (real-time).
|
|
52181
|
-
* @param options - Configuración opcional (fallback, retry, gate).
|
|
52182
|
-
*
|
|
52183
|
-
* @example
|
|
52184
|
-
* ```typescript
|
|
52185
|
-
* // one-shot (recomendado por defecto)
|
|
52186
|
-
* private readonly stream = createRefreshableStream(
|
|
52187
|
-
* () => from(this.svc.getAllOnce()),
|
|
52188
|
-
* );
|
|
52189
|
-
*
|
|
52190
|
-
* // real-time (inbox)
|
|
52191
|
-
* private readonly stream = createRefreshableStream(
|
|
52192
|
-
* () => this.notifs.getAll(50),
|
|
52193
|
-
* );
|
|
52194
|
-
*
|
|
52195
|
-
* readonly items = this.stream.data;
|
|
52196
|
-
* readonly loading = this.stream.loading;
|
|
52197
|
-
*
|
|
52198
|
-
* ionViewWillEnter() {
|
|
52199
|
-
* this.pageRefresh.register(() => this.stream.reload());
|
|
52200
|
-
* }
|
|
52201
|
-
* ```
|
|
52202
|
-
*/
|
|
52203
|
-
function createRefreshableStream(factory, options) {
|
|
52204
|
-
const injector = options?.injector ?? inject(Injector);
|
|
52205
|
-
const fallback = (options?.fallback ?? null);
|
|
52206
|
-
const retryCount = options?.retryCount ?? 4;
|
|
52207
|
-
const retryBaseDelayMs = options?.retryBaseDelayMs ?? 500;
|
|
52208
|
-
const gateOnFirebaseAuth = options?.gateOnFirebaseAuth ?? true;
|
|
52209
|
-
const authGateTimeoutMs = options?.authGateTimeoutMs ?? 20000;
|
|
52210
|
-
const firstEmitTimeoutMs = options?.firstEmitTimeoutMs ?? 20000;
|
|
52211
|
-
// FirebaseService es opcional — sin él (apps sin Firebase) no se gatea.
|
|
52212
|
-
const firebase = gateOnFirebaseAuth ? injector.get(FirebaseService, null) : null;
|
|
52213
|
-
const _reload = signal(0);
|
|
52214
|
-
const _loading = signal(true);
|
|
52215
|
-
const _error = signal(false);
|
|
52216
|
-
// Gate de Firebase Auth: el stream no abre la primera suscripción hasta que
|
|
52217
|
-
// la sesión esté confirmada. `race` con un `timer` de seguridad — si la
|
|
52218
|
-
// sesión NUNCA se establece (token inválido, signInWithFirebase falló), el
|
|
52219
|
-
// gate igual se libera tras `authGateTimeoutMs` y la factory procede: si no
|
|
52220
|
-
// hay permiso terminará en estado `error`, jamás en skeleton infinito.
|
|
52221
|
-
// Tag los dos inputs del `race` para distinguir en logs cuál ganó: la sesión
|
|
52222
|
-
// de Firebase Auth (`auth-ready`) o el timeout de seguridad (`timeout`).
|
|
52223
|
-
const authGate$ = firebase
|
|
52224
|
-
? race(firebase.firebaseAuthReady$.pipe(map$1(() => 'auth-ready')), timer(authGateTimeoutMs).pipe(map$1(() => 'timeout'))).pipe(take(1), tap(winner => {
|
|
52225
|
-
if (winner === 'timeout') {
|
|
52226
|
-
console.warn(`[RefreshStream] auth gate resolved by TIMEOUT after ${authGateTimeoutMs}ms ` +
|
|
52227
|
-
'(Firebase Auth session never confirmed)');
|
|
52228
|
-
}
|
|
52229
|
-
else {
|
|
52230
|
-
console.log('[RefreshStream] auth gate resolved by firebaseAuthReady$ (session ready)');
|
|
52231
|
-
}
|
|
52232
|
-
}))
|
|
52233
|
-
: of(true);
|
|
52234
|
-
const data = toSignal(toObservable(_reload, { injector }).pipe(tap(() => {
|
|
52235
|
-
_loading.set(true);
|
|
52236
|
-
_error.set(false);
|
|
52237
|
-
}),
|
|
52238
|
-
// Esperar a Firebase Auth listo antes de cada (re)suscripción. Como
|
|
52239
|
-
// `firebaseAuthReady$` completa tras la primera emisión, las reloads
|
|
52240
|
-
// posteriores resuelven el gate de inmediato (auth ya está listo).
|
|
52241
|
-
switchMap(() => authGate$.pipe(switchMap(() => factory().pipe(tap(() => {
|
|
52242
|
-
_loading.set(false);
|
|
52243
|
-
_error.set(false);
|
|
52244
|
-
console.log('[RefreshStream] factory emitted first value — loading=false');
|
|
52245
|
-
}),
|
|
52246
|
-
// Backoff corto exponencial acotado — solo red de seguridad para
|
|
52247
|
-
// reconexiones transitorias, NO para cubrir cold start.
|
|
52248
|
-
retry({
|
|
52249
|
-
count: retryCount,
|
|
52250
|
-
delay: (_err, attempt) => timer(Math.min(retryBaseDelayMs * 2 ** (attempt - 1), 4000)),
|
|
52251
|
-
}),
|
|
52252
|
-
// Techo absoluto para la PRIMERA emisión. Si la factory no emite
|
|
52253
|
-
// ni valor ni error (servicio nunca inicializado → `EMPTY` →
|
|
52254
|
-
// silencio), `timeout` dispara un error → `catchError` → estado
|
|
52255
|
-
// `error`. Nunca skeleton perpetuo. Va DESPUÉS de `retry` para
|
|
52256
|
-
// que cuente el tiempo total, no por intento.
|
|
52257
|
-
timeout({ first: firstEmitTimeoutMs }), catchError(err => {
|
|
52258
|
-
_loading.set(false);
|
|
52259
|
-
_error.set(true);
|
|
52260
|
-
console.error(`[RefreshStream] factory failed/timed out → error state: ${err instanceof Error ? err.message : String(err)}`);
|
|
52261
|
-
return of(fallback);
|
|
52262
|
-
})))))), { initialValue: null, injector });
|
|
52263
|
-
return {
|
|
52264
|
-
data,
|
|
52265
|
-
loading: _loading.asReadonly(),
|
|
52266
|
-
error: _error.asReadonly(),
|
|
52267
|
-
reload: () => _reload.update(n => n + 1),
|
|
52268
|
-
};
|
|
52269
|
-
}
|
|
52270
|
-
|
|
52271
53080
|
/**
|
|
52272
53081
|
* Refreshable Stream
|
|
52273
53082
|
*
|
|
@@ -60432,5 +61241,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
60432
61241
|
* Generated bundle index. Do not edit.
|
|
60433
61242
|
*/
|
|
60434
61243
|
|
|
60435
|
-
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
61244
|
+
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
|
|
60436
61245
|
//# sourceMappingURL=valtech-components.mjs.map
|