valtech-components 4.0.224 → 4.0.226
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/chat-composer/chat-composer.component.mjs +238 -0
- package/esm2022/lib/components/molecules/conversation-list-item/conversation-list-item.component.mjs +108 -0
- package/esm2022/lib/components/molecules/message-bubble/message-bubble.component.mjs +259 -191
- package/esm2022/lib/components/molecules/typing-indicator/typing-indicator.component.mjs +11 -12
- package/esm2022/lib/components/organisms/chat-window/chat-window.component.mjs +217 -179
- package/esm2022/lib/components/organisms/thread-panel/thread-panel.component.mjs +1 -1
- package/esm2022/lib/services/chat/types.mjs +1 -1
- package/esm2022/lib/services/i18n/default-content.mjs +5 -1
- package/esm2022/lib/shared/utils/datetime.mjs +54 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +4 -2
- package/fesm2022/valtech-components.mjs +869 -375
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/chat-composer/chat-composer.component.d.ts +46 -0
- package/lib/components/molecules/conversation-list-item/conversation-list-item.component.d.ts +30 -0
- package/lib/components/molecules/message-bubble/message-bubble.component.d.ts +33 -32
- package/lib/components/molecules/typing-indicator/typing-indicator.component.d.ts +5 -4
- package/lib/components/organisms/chat-window/chat-window.component.d.ts +52 -51
- package/lib/services/chat/types.d.ts +6 -2
- package/lib/shared/utils/datetime.d.ts +19 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +3 -1
|
@@ -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, IonRadio, IonRadioGroup, IonTextarea, IonDatetime, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonInput, IonChip, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonInfiniteScroll, IonInfiniteScrollContent, IonNote, IonDatetimeButton, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, 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, cloudUploadOutline, textOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, newspaperOutline, 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, printOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, codeSlashOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, shareSocialOutline, helpCircleOutline, menuOutline, stopCircleOutline, videocamOffOutline, checkmarkDone,
|
|
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, cloudUploadOutline, textOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, newspaperOutline, 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, printOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, codeSlashOutline, notificationsOffOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, shareSocialOutline, helpCircleOutline, menuOutline, stopCircleOutline, videocamOffOutline, checkmarkDone, arrowUndoOutline, micOutline, arrowUpOutline } 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';
|
|
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
|
|
|
56
56
|
* Current version of valtech-components.
|
|
57
57
|
* This is automatically updated during the publish process.
|
|
58
58
|
*/
|
|
59
|
-
const VERSION = '4.0.
|
|
59
|
+
const VERSION = '4.0.226';
|
|
60
60
|
|
|
61
61
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
62
62
|
let isRefreshing = false;
|
|
@@ -1538,6 +1538,59 @@ function getTimeOfDayKey(date = new Date()) {
|
|
|
1538
1538
|
return 'goodAfternoon';
|
|
1539
1539
|
return 'goodEvening';
|
|
1540
1540
|
}
|
|
1541
|
+
/** Convierte un valor de fecha (Date | ISO string | undefined) a Date válido o null. */
|
|
1542
|
+
function toDate(value) {
|
|
1543
|
+
if (!value)
|
|
1544
|
+
return null;
|
|
1545
|
+
const d = value instanceof Date ? value : new Date(value);
|
|
1546
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
1547
|
+
}
|
|
1548
|
+
/** Diferencia en días-calendario entre `from` y `to` (to - from), ignorando la hora. */
|
|
1549
|
+
function calendarDayDiff(from, to) {
|
|
1550
|
+
const a = new Date(from.getFullYear(), from.getMonth(), from.getDate());
|
|
1551
|
+
const b = new Date(to.getFullYear(), to.getMonth(), to.getDate());
|
|
1552
|
+
return Math.round((b.getTime() - a.getTime()) / 86_400_000);
|
|
1553
|
+
}
|
|
1554
|
+
/** Hora corta local, ej. "14:05". */
|
|
1555
|
+
function formatClockTime(value, locale = 'es-CL') {
|
|
1556
|
+
const d = toDate(value);
|
|
1557
|
+
if (!d)
|
|
1558
|
+
return '';
|
|
1559
|
+
return d.toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' });
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Etiqueta corta para listas de conversaciones (estilo Telegram/WhatsApp):
|
|
1563
|
+
* hoy → hora ("14:05"), ayer → label `yesterday`, esta semana → día de semana,
|
|
1564
|
+
* más viejo → fecha corta. `labels.today/yesterday` se inyectan ya traducidos.
|
|
1565
|
+
*/
|
|
1566
|
+
function formatRelativeTime(value, locale = 'es-CL', labels = {}) {
|
|
1567
|
+
const d = toDate(value);
|
|
1568
|
+
if (!d)
|
|
1569
|
+
return '';
|
|
1570
|
+
const diff = calendarDayDiff(d, new Date());
|
|
1571
|
+
if (diff <= 0)
|
|
1572
|
+
return formatClockTime(d, locale);
|
|
1573
|
+
if (diff === 1)
|
|
1574
|
+
return labels.yesterday ?? 'Ayer';
|
|
1575
|
+
if (diff < 7)
|
|
1576
|
+
return d.toLocaleDateString(locale, { weekday: 'short' });
|
|
1577
|
+
return d.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: '2-digit' });
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Etiqueta para separadores de fecha dentro de un chat: "Hoy" / "Ayer" /
|
|
1581
|
+
* fecha larga ("12 de junio de 2026"). `labels.today/yesterday` ya traducidos.
|
|
1582
|
+
*/
|
|
1583
|
+
function formatDateSeparator(value, locale = 'es-CL', labels = {}) {
|
|
1584
|
+
const d = toDate(value);
|
|
1585
|
+
if (!d)
|
|
1586
|
+
return '';
|
|
1587
|
+
const diff = calendarDayDiff(d, new Date());
|
|
1588
|
+
if (diff <= 0)
|
|
1589
|
+
return labels.today ?? 'Hoy';
|
|
1590
|
+
if (diff === 1)
|
|
1591
|
+
return labels.yesterday ?? 'Ayer';
|
|
1592
|
+
return d.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' });
|
|
1593
|
+
}
|
|
1541
1594
|
|
|
1542
1595
|
/**
|
|
1543
1596
|
* Servicio de internacionalización basado en Angular Signals.
|
|
@@ -7030,6 +7083,8 @@ const VALTECH_DEFAULT_CONTENT = {
|
|
|
7030
7083
|
loading: 'Cargando...',
|
|
7031
7084
|
search: 'Buscar',
|
|
7032
7085
|
learnMore: 'Saber más',
|
|
7086
|
+
today: 'Hoy',
|
|
7087
|
+
yesterday: 'Ayer',
|
|
7033
7088
|
// Estados
|
|
7034
7089
|
success: 'Éxito',
|
|
7035
7090
|
error: 'Error',
|
|
@@ -7187,6 +7242,8 @@ const VALTECH_DEFAULT_CONTENT = {
|
|
|
7187
7242
|
loading: 'Loading...',
|
|
7188
7243
|
search: 'Search',
|
|
7189
7244
|
learnMore: 'Learn more',
|
|
7245
|
+
today: 'Today',
|
|
7246
|
+
yesterday: 'Yesterday',
|
|
7190
7247
|
// States
|
|
7191
7248
|
success: 'Success',
|
|
7192
7249
|
error: 'Error',
|
|
@@ -72286,241 +72343,308 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72286
72343
|
*/
|
|
72287
72344
|
|
|
72288
72345
|
addIcons({
|
|
72346
|
+
alertCircle,
|
|
72289
72347
|
checkmark,
|
|
72290
72348
|
checkmarkDone,
|
|
72349
|
+
createOutline,
|
|
72350
|
+
documentOutline,
|
|
72351
|
+
happyOutline,
|
|
72352
|
+
arrowUndoOutline,
|
|
72291
72353
|
timeOutline,
|
|
72292
|
-
|
|
72293
|
-
pencil,
|
|
72294
|
-
trash,
|
|
72295
|
-
returnDownBackOutline,
|
|
72354
|
+
trashOutline,
|
|
72296
72355
|
});
|
|
72297
|
-
const
|
|
72356
|
+
const MESSAGE_BUBBLE_I18N = {
|
|
72298
72357
|
es: {
|
|
72299
|
-
messageDeleted: '[mensaje eliminado]',
|
|
72300
|
-
edited: '(editado)',
|
|
72301
|
-
react: 'Reaccionar',
|
|
72302
72358
|
reply: 'Responder',
|
|
72303
72359
|
edit: 'Editar',
|
|
72304
72360
|
delete: 'Eliminar',
|
|
72361
|
+
react: 'Reaccionar',
|
|
72362
|
+
deleted: 'Mensaje eliminado',
|
|
72363
|
+
edited: 'editado',
|
|
72305
72364
|
},
|
|
72306
72365
|
en: {
|
|
72307
|
-
messageDeleted: '[message deleted]',
|
|
72308
|
-
edited: '(edited)',
|
|
72309
|
-
react: 'React',
|
|
72310
72366
|
reply: 'Reply',
|
|
72311
72367
|
edit: 'Edit',
|
|
72312
72368
|
delete: 'Delete',
|
|
72369
|
+
react: 'React',
|
|
72370
|
+
deleted: 'Message deleted',
|
|
72371
|
+
edited: 'edited',
|
|
72313
72372
|
},
|
|
72314
72373
|
};
|
|
72315
72374
|
/**
|
|
72316
|
-
* val-message-bubble
|
|
72375
|
+
* val-message-bubble — burbuja de mensaje presentacional (signal inputs).
|
|
72317
72376
|
*
|
|
72318
|
-
*
|
|
72319
|
-
*
|
|
72320
|
-
*
|
|
72377
|
+
* Sin lógica de negocio: el contenedor (`val-chat-window`) decide agrupación y
|
|
72378
|
+
* pasa flags (showAvatar/showName/tail). Las acciones (responder/editar/borrar/
|
|
72379
|
+
* reaccionar) NO están siempre visibles: se revelan en hover (desktop) o al tocar
|
|
72380
|
+
* la burbuja (móvil), y se emiten por un único `(action)` tipado.
|
|
72321
72381
|
*
|
|
72322
|
-
*
|
|
72323
|
-
*
|
|
72324
|
-
* [props]="msg"
|
|
72325
|
-
* (reactionClick)="onReaction($event)"
|
|
72326
|
-
* (replyClick)="onReply($event)"
|
|
72327
|
-
* (deleteClick)="onDelete($event)"
|
|
72328
|
-
* />
|
|
72382
|
+
* Read receipts estilo WhatsApp: sending → reloj, sent → ✓, delivered/read → ✓✓
|
|
72383
|
+
* (read en color), failed → alerta.
|
|
72329
72384
|
*/
|
|
72330
72385
|
class MessageBubbleComponent {
|
|
72331
72386
|
constructor() {
|
|
72332
72387
|
this.i18n = inject(I18nService);
|
|
72333
|
-
this.
|
|
72334
|
-
this.
|
|
72335
|
-
this.
|
|
72336
|
-
this.
|
|
72337
|
-
|
|
72338
|
-
|
|
72339
|
-
|
|
72340
|
-
|
|
72341
|
-
|
|
72342
|
-
|
|
72343
|
-
|
|
72344
|
-
|
|
72345
|
-
|
|
72346
|
-
|
|
72347
|
-
|
|
72348
|
-
|
|
72349
|
-
|
|
72350
|
-
|
|
72351
|
-
|
|
72352
|
-
|
|
72353
|
-
|
|
72354
|
-
|
|
72355
|
-
|
|
72356
|
-
|
|
72357
|
-
|
|
72358
|
-
|
|
72359
|
-
|
|
72360
|
-
|
|
72361
|
-
|
|
72362
|
-
|
|
72363
|
-
|
|
72364
|
-
|
|
72365
|
-
}
|
|
72366
|
-
}
|
|
72367
|
-
onReactionClick(reaction) {
|
|
72368
|
-
this.reactionClick.emit({ token: reaction.token, msgId: this.props.msgId });
|
|
72388
|
+
this.msg = input.required();
|
|
72389
|
+
this.showAvatar = input(false);
|
|
72390
|
+
this.showName = input(false);
|
|
72391
|
+
this.tail = input(true);
|
|
72392
|
+
this.locale = input('es-CL');
|
|
72393
|
+
this.action = output();
|
|
72394
|
+
this.actionsOpen = signal(false);
|
|
72395
|
+
this.time = computed(() => formatClockTime(this.msg().createdAt, this.locale()));
|
|
72396
|
+
this.initials = computed(() => {
|
|
72397
|
+
const name = this.msg().senderName?.trim() ?? '';
|
|
72398
|
+
if (!name)
|
|
72399
|
+
return '?';
|
|
72400
|
+
const parts = name.split(/\s+/);
|
|
72401
|
+
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
72402
|
+
});
|
|
72403
|
+
this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
|
|
72404
|
+
this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/')));
|
|
72405
|
+
this.statusIcon = computed(() => {
|
|
72406
|
+
switch (this.msg().status) {
|
|
72407
|
+
case 'sending':
|
|
72408
|
+
return 'time-outline';
|
|
72409
|
+
case 'failed':
|
|
72410
|
+
return 'alert-circle';
|
|
72411
|
+
case 'delivered':
|
|
72412
|
+
case 'read':
|
|
72413
|
+
return 'checkmark-done';
|
|
72414
|
+
case 'sent':
|
|
72415
|
+
default:
|
|
72416
|
+
return 'checkmark';
|
|
72417
|
+
}
|
|
72418
|
+
});
|
|
72419
|
+
this.i18n.registerDefaults('MessageBubble', MESSAGE_BUBBLE_I18N);
|
|
72369
72420
|
}
|
|
72370
|
-
|
|
72371
|
-
this.
|
|
72421
|
+
toggleActions() {
|
|
72422
|
+
if (this.msg().isDeleted)
|
|
72423
|
+
return;
|
|
72424
|
+
this.actionsOpen.update(v => !v);
|
|
72372
72425
|
}
|
|
72373
|
-
|
|
72374
|
-
this.
|
|
72426
|
+
emit(type, token) {
|
|
72427
|
+
this.actionsOpen.set(false);
|
|
72428
|
+
this.action.emit({ type, msgId: this.msg().msgId, token });
|
|
72375
72429
|
}
|
|
72376
|
-
|
|
72377
|
-
this.
|
|
72430
|
+
t(key) {
|
|
72431
|
+
return this.i18n.t(key, 'MessageBubble');
|
|
72378
72432
|
}
|
|
72379
72433
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72380
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MessageBubbleComponent, isStandalone: true, selector: "val-message-bubble", inputs: {
|
|
72381
|
-
<div class="
|
|
72382
|
-
@if (
|
|
72383
|
-
<
|
|
72434
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MessageBubbleComponent, isStandalone: true, selector: "val-message-bubble", inputs: { msg: { classPropertyName: "msg", publicName: "msg", isSignal: true, isRequired: true, transformFunction: null }, showAvatar: { classPropertyName: "showAvatar", publicName: "showAvatar", isSignal: true, isRequired: false, transformFunction: null }, showName: { classPropertyName: "showName", publicName: "showName", isSignal: true, isRequired: false, transformFunction: null }, tail: { classPropertyName: "tail", publicName: "tail", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, ngImport: i0, template: `
|
|
72435
|
+
<div class="row" [class.mine]="msg().isMine" [class.tail]="tail()" [class.with-avatar]="showAvatar()">
|
|
72436
|
+
@if (showAvatar() && !msg().isMine) {
|
|
72437
|
+
<div class="avatar" [class.hidden]="!tail()">
|
|
72438
|
+
@if (msg().senderAvatar) {
|
|
72439
|
+
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72440
|
+
} @else {
|
|
72441
|
+
<span>{{ initials() }}</span>
|
|
72442
|
+
}
|
|
72443
|
+
</div>
|
|
72384
72444
|
}
|
|
72385
72445
|
|
|
72386
|
-
<div class="bubble">
|
|
72387
|
-
|
|
72388
|
-
|
|
72389
|
-
|
|
72390
|
-
|
|
72391
|
-
|
|
72392
|
-
|
|
72393
|
-
|
|
72394
|
-
|
|
72395
|
-
|
|
72396
|
-
} @else {
|
|
72397
|
-
<div class="bubble-body">{{ props.body }}</div>
|
|
72398
|
-
}
|
|
72399
|
-
|
|
72400
|
-
<div class="bubble-meta">
|
|
72401
|
-
<span class="timestamp">{{ formatTime(props.createdAt) }}</span>
|
|
72402
|
-
@if (props.isEdited && !props.isDeleted) {
|
|
72403
|
-
<span class="edited-label">{{ editedLabel() }}</span>
|
|
72404
|
-
}
|
|
72405
|
-
@if (props.isMine && props.status) {
|
|
72406
|
-
<ion-icon
|
|
72407
|
-
[name]="statusIcon()"
|
|
72408
|
-
[class]="'status-icon ' + (props.status ?? '')"
|
|
72409
|
-
aria-hidden="true"
|
|
72410
|
-
></ion-icon>
|
|
72446
|
+
<div class="bubble-wrap">
|
|
72447
|
+
<div
|
|
72448
|
+
class="bubble"
|
|
72449
|
+
[class.deleted]="msg().isDeleted"
|
|
72450
|
+
(click)="toggleActions()"
|
|
72451
|
+
(keyup.enter)="toggleActions()"
|
|
72452
|
+
tabindex="0"
|
|
72453
|
+
>
|
|
72454
|
+
@if (showName() && !msg().isMine && !msg().isDeleted) {
|
|
72455
|
+
<span class="name">{{ msg().senderName }}</span>
|
|
72411
72456
|
}
|
|
72412
|
-
</div>
|
|
72413
|
-
</div>
|
|
72414
72457
|
|
|
72415
|
-
|
|
72416
|
-
|
|
72417
|
-
|
|
72418
|
-
|
|
72419
|
-
|
|
72420
|
-
@if (reaction.count > 0) {
|
|
72421
|
-
<span class="reaction-count">{{ reaction.count }}</span>
|
|
72422
|
-
}
|
|
72423
|
-
</button>
|
|
72458
|
+
@if (msg().replyTo && !msg().isDeleted) {
|
|
72459
|
+
<div class="reply-preview">
|
|
72460
|
+
<span class="reply-name">{{ msg().replyTo!.senderName }}</span>
|
|
72461
|
+
<span class="reply-body">{{ msg().replyTo!.body }}</span>
|
|
72462
|
+
</div>
|
|
72424
72463
|
}
|
|
72425
|
-
</div>
|
|
72426
|
-
}
|
|
72427
72464
|
|
|
72428
|
-
|
|
72429
|
-
|
|
72430
|
-
|
|
72431
|
-
|
|
72432
|
-
|
|
72433
|
-
|
|
72434
|
-
|
|
72435
|
-
|
|
72436
|
-
|
|
72437
|
-
|
|
72438
|
-
|
|
72439
|
-
|
|
72465
|
+
@if (msg().isDeleted) {
|
|
72466
|
+
<span class="body deleted-text">{{ t('deleted') }}</span>
|
|
72467
|
+
} @else {
|
|
72468
|
+
@if (imageAttachments().length > 0) {
|
|
72469
|
+
<div class="attachments">
|
|
72470
|
+
@for (att of imageAttachments(); track att.url) {
|
|
72471
|
+
<img class="att-image" [src]="att.url" [alt]="att.name" />
|
|
72472
|
+
}
|
|
72473
|
+
</div>
|
|
72474
|
+
}
|
|
72475
|
+
@if (fileAttachments().length > 0) {
|
|
72476
|
+
<div class="files">
|
|
72477
|
+
@for (att of fileAttachments(); track att.url) {
|
|
72478
|
+
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72479
|
+
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72480
|
+
<span>{{ att.name }}</span>
|
|
72481
|
+
</a>
|
|
72482
|
+
}
|
|
72483
|
+
</div>
|
|
72484
|
+
}
|
|
72485
|
+
@if (msg().body) {
|
|
72486
|
+
<span class="body">{{ msg().body }}</span>
|
|
72487
|
+
}
|
|
72440
72488
|
}
|
|
72489
|
+
|
|
72490
|
+
<span class="meta">
|
|
72491
|
+
@if (msg().isEdited && !msg().isDeleted) {
|
|
72492
|
+
<span class="edited">{{ t('edited') }}</span>
|
|
72493
|
+
}
|
|
72494
|
+
<span class="time">{{ time() }}</span>
|
|
72495
|
+
@if (msg().isMine && !msg().isDeleted) {
|
|
72496
|
+
<ion-icon
|
|
72497
|
+
class="status"
|
|
72498
|
+
[class.read]="msg().status === 'read'"
|
|
72499
|
+
[class.failed]="msg().status === 'failed'"
|
|
72500
|
+
[name]="statusIcon()"
|
|
72501
|
+
aria-hidden="true"
|
|
72502
|
+
/>
|
|
72503
|
+
}
|
|
72504
|
+
</span>
|
|
72441
72505
|
</div>
|
|
72442
|
-
}
|
|
72443
|
-
</div>
|
|
72444
|
-
`, isInline: true, styles: [".message-bubble-wrapper{display:flex;flex-direction:column;align-items:flex-start;margin-bottom:4px}.message-bubble-wrapper.is-mine{align-items:flex-end}.message-bubble-wrapper.is-mine .bubble{background:var(--ion-color-primary);color:#fff;border-radius:18px 18px 4px}.message-bubble-wrapper.is-mine .bubble-meta{justify-content:flex-end}.message-bubble-wrapper:not(.is-mine) .bubble{background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-text-color, #000);border-radius:18px 18px 18px 4px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.sender-name{font-size:.75rem;font-weight:600;color:var(--ion-color-medium, #92949c);margin:0 0 2px 12px}.bubble{max-width:75%;min-width:60px;padding:10px 14px;word-break:break-word;position:relative;box-shadow:0 1px 2px #0000000f}.reply-preview{background:#0000000f;border-left:3px solid var(--ion-color-primary);border-radius:6px;padding:6px 8px;margin-bottom:6px;font-size:.75rem;opacity:.85}.reply-preview .reply-sender{font-weight:700;margin-bottom:2px}.reply-preview .reply-body{opacity:.8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px}.bubble-body{font-size:.9375rem;line-height:1.4;white-space:pre-wrap}.deleted-text{font-size:.875rem;font-style:italic;opacity:.6}.bubble-meta{display:flex;align-items:center;gap:4px;margin-top:4px;font-size:.6875rem;opacity:.7}.edited-label{font-style:italic}.status-icon{font-size:.75rem}.status-icon.sending{color:var(--ion-color-medium)}.status-icon.sent{color:#fff}.status-icon.failed{color:var(--ion-color-danger)}.reactions-bar{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.reactions-bar .reaction-btn{display:inline-flex;align-items:center;gap:3px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:12px;padding:2px 8px;font-size:.8125rem;cursor:pointer;transition:background .15s ease}.reactions-bar .reaction-btn.active{background:var(--ion-color-primary-tint, #4c8dff);color:#fff;border-color:var(--ion-color-primary)}.reactions-bar .reaction-btn .reaction-count{font-weight:600}.bubble-actions{display:flex;gap:4px;margin-top:2px}.bubble-actions ion-button{--padding-start: 6px;--padding-end: 6px;font-size:.75rem}\n"], dependencies: [{ 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"] }] }); }
|
|
72445
|
-
}
|
|
72446
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72447
|
-
type: Component,
|
|
72448
|
-
args: [{ selector: 'val-message-bubble', standalone: true, imports: [CommonModule, IonIcon, IonButton], template: `
|
|
72449
|
-
<div class="message-bubble-wrapper" [class.is-mine]="props.isMine">
|
|
72450
|
-
@if (!props.isMine && !props.isDeleted) {
|
|
72451
|
-
<span class="sender-name">{{ props.senderName }}</span>
|
|
72452
|
-
}
|
|
72453
72506
|
|
|
72454
|
-
|
|
72455
|
-
|
|
72456
|
-
|
|
72457
|
-
|
|
72458
|
-
|
|
72507
|
+
@if (msg().reactions && msg().reactions!.length > 0) {
|
|
72508
|
+
<div class="reactions">
|
|
72509
|
+
@for (r of msg().reactions!; track r.token) {
|
|
72510
|
+
<button class="reaction" [class.active]="r.active" (click)="emit('react', r.token)">
|
|
72511
|
+
<span>{{ r.token }}</span>
|
|
72512
|
+
<span class="count">{{ r.count }}</span>
|
|
72513
|
+
</button>
|
|
72514
|
+
}
|
|
72459
72515
|
</div>
|
|
72460
72516
|
}
|
|
72461
72517
|
|
|
72462
|
-
@if (
|
|
72463
|
-
<
|
|
72464
|
-
|
|
72465
|
-
|
|
72518
|
+
@if (!msg().isDeleted) {
|
|
72519
|
+
<div class="actions" [class.open]="actionsOpen()">
|
|
72520
|
+
<button class="act" [attr.aria-label]="t('react')" (click)="emit('react', '👍')">
|
|
72521
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72522
|
+
</button>
|
|
72523
|
+
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72524
|
+
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72525
|
+
</button>
|
|
72526
|
+
@if (msg().isMine) {
|
|
72527
|
+
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72528
|
+
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72529
|
+
</button>
|
|
72530
|
+
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72531
|
+
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72532
|
+
</button>
|
|
72533
|
+
}
|
|
72534
|
+
</div>
|
|
72466
72535
|
}
|
|
72467
|
-
|
|
72468
|
-
<div class="bubble-meta">
|
|
72469
|
-
<span class="timestamp">{{ formatTime(props.createdAt) }}</span>
|
|
72470
|
-
@if (props.isEdited && !props.isDeleted) {
|
|
72471
|
-
<span class="edited-label">{{ editedLabel() }}</span>
|
|
72472
|
-
}
|
|
72473
|
-
@if (props.isMine && props.status) {
|
|
72474
|
-
<ion-icon
|
|
72475
|
-
[name]="statusIcon()"
|
|
72476
|
-
[class]="'status-icon ' + (props.status ?? '')"
|
|
72477
|
-
aria-hidden="true"
|
|
72478
|
-
></ion-icon>
|
|
72479
|
-
}
|
|
72480
|
-
</div>
|
|
72481
72536
|
</div>
|
|
72482
|
-
|
|
72483
|
-
|
|
72484
|
-
|
|
72485
|
-
|
|
72486
|
-
|
|
72487
|
-
|
|
72488
|
-
|
|
72489
|
-
|
|
72490
|
-
|
|
72491
|
-
|
|
72537
|
+
</div>
|
|
72538
|
+
`, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
72539
|
+
}
|
|
72540
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72541
|
+
type: Component,
|
|
72542
|
+
args: [{ selector: 'val-message-bubble', standalone: true, imports: [CommonModule, IonIcon], template: `
|
|
72543
|
+
<div class="row" [class.mine]="msg().isMine" [class.tail]="tail()" [class.with-avatar]="showAvatar()">
|
|
72544
|
+
@if (showAvatar() && !msg().isMine) {
|
|
72545
|
+
<div class="avatar" [class.hidden]="!tail()">
|
|
72546
|
+
@if (msg().senderAvatar) {
|
|
72547
|
+
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72548
|
+
} @else {
|
|
72549
|
+
<span>{{ initials() }}</span>
|
|
72492
72550
|
}
|
|
72493
72551
|
</div>
|
|
72494
72552
|
}
|
|
72495
72553
|
|
|
72496
|
-
|
|
72497
|
-
<div
|
|
72498
|
-
|
|
72499
|
-
|
|
72500
|
-
|
|
72501
|
-
|
|
72502
|
-
|
|
72503
|
-
|
|
72504
|
-
|
|
72505
|
-
<
|
|
72506
|
-
|
|
72507
|
-
|
|
72554
|
+
<div class="bubble-wrap">
|
|
72555
|
+
<div
|
|
72556
|
+
class="bubble"
|
|
72557
|
+
[class.deleted]="msg().isDeleted"
|
|
72558
|
+
(click)="toggleActions()"
|
|
72559
|
+
(keyup.enter)="toggleActions()"
|
|
72560
|
+
tabindex="0"
|
|
72561
|
+
>
|
|
72562
|
+
@if (showName() && !msg().isMine && !msg().isDeleted) {
|
|
72563
|
+
<span class="name">{{ msg().senderName }}</span>
|
|
72564
|
+
}
|
|
72565
|
+
|
|
72566
|
+
@if (msg().replyTo && !msg().isDeleted) {
|
|
72567
|
+
<div class="reply-preview">
|
|
72568
|
+
<span class="reply-name">{{ msg().replyTo!.senderName }}</span>
|
|
72569
|
+
<span class="reply-body">{{ msg().replyTo!.body }}</span>
|
|
72570
|
+
</div>
|
|
72508
72571
|
}
|
|
72572
|
+
|
|
72573
|
+
@if (msg().isDeleted) {
|
|
72574
|
+
<span class="body deleted-text">{{ t('deleted') }}</span>
|
|
72575
|
+
} @else {
|
|
72576
|
+
@if (imageAttachments().length > 0) {
|
|
72577
|
+
<div class="attachments">
|
|
72578
|
+
@for (att of imageAttachments(); track att.url) {
|
|
72579
|
+
<img class="att-image" [src]="att.url" [alt]="att.name" />
|
|
72580
|
+
}
|
|
72581
|
+
</div>
|
|
72582
|
+
}
|
|
72583
|
+
@if (fileAttachments().length > 0) {
|
|
72584
|
+
<div class="files">
|
|
72585
|
+
@for (att of fileAttachments(); track att.url) {
|
|
72586
|
+
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72587
|
+
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72588
|
+
<span>{{ att.name }}</span>
|
|
72589
|
+
</a>
|
|
72590
|
+
}
|
|
72591
|
+
</div>
|
|
72592
|
+
}
|
|
72593
|
+
@if (msg().body) {
|
|
72594
|
+
<span class="body">{{ msg().body }}</span>
|
|
72595
|
+
}
|
|
72596
|
+
}
|
|
72597
|
+
|
|
72598
|
+
<span class="meta">
|
|
72599
|
+
@if (msg().isEdited && !msg().isDeleted) {
|
|
72600
|
+
<span class="edited">{{ t('edited') }}</span>
|
|
72601
|
+
}
|
|
72602
|
+
<span class="time">{{ time() }}</span>
|
|
72603
|
+
@if (msg().isMine && !msg().isDeleted) {
|
|
72604
|
+
<ion-icon
|
|
72605
|
+
class="status"
|
|
72606
|
+
[class.read]="msg().status === 'read'"
|
|
72607
|
+
[class.failed]="msg().status === 'failed'"
|
|
72608
|
+
[name]="statusIcon()"
|
|
72609
|
+
aria-hidden="true"
|
|
72610
|
+
/>
|
|
72611
|
+
}
|
|
72612
|
+
</span>
|
|
72509
72613
|
</div>
|
|
72510
|
-
|
|
72614
|
+
|
|
72615
|
+
@if (msg().reactions && msg().reactions!.length > 0) {
|
|
72616
|
+
<div class="reactions">
|
|
72617
|
+
@for (r of msg().reactions!; track r.token) {
|
|
72618
|
+
<button class="reaction" [class.active]="r.active" (click)="emit('react', r.token)">
|
|
72619
|
+
<span>{{ r.token }}</span>
|
|
72620
|
+
<span class="count">{{ r.count }}</span>
|
|
72621
|
+
</button>
|
|
72622
|
+
}
|
|
72623
|
+
</div>
|
|
72624
|
+
}
|
|
72625
|
+
|
|
72626
|
+
@if (!msg().isDeleted) {
|
|
72627
|
+
<div class="actions" [class.open]="actionsOpen()">
|
|
72628
|
+
<button class="act" [attr.aria-label]="t('react')" (click)="emit('react', '👍')">
|
|
72629
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72630
|
+
</button>
|
|
72631
|
+
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72632
|
+
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72633
|
+
</button>
|
|
72634
|
+
@if (msg().isMine) {
|
|
72635
|
+
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72636
|
+
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72637
|
+
</button>
|
|
72638
|
+
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72639
|
+
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72640
|
+
</button>
|
|
72641
|
+
}
|
|
72642
|
+
</div>
|
|
72643
|
+
}
|
|
72644
|
+
</div>
|
|
72511
72645
|
</div>
|
|
72512
|
-
`, styles: [".
|
|
72513
|
-
}], ctorParameters: () => []
|
|
72514
|
-
type: Input
|
|
72515
|
-
}], reactionClick: [{
|
|
72516
|
-
type: Output
|
|
72517
|
-
}], replyClick: [{
|
|
72518
|
-
type: Output
|
|
72519
|
-
}], deleteClick: [{
|
|
72520
|
-
type: Output
|
|
72521
|
-
}], editClick: [{
|
|
72522
|
-
type: Output
|
|
72523
|
-
}] } });
|
|
72646
|
+
`, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"] }]
|
|
72647
|
+
}], ctorParameters: () => [] });
|
|
72524
72648
|
|
|
72525
72649
|
addIcons({ sendOutline });
|
|
72526
72650
|
const CHAT_INPUT_I18N = {
|
|
@@ -72672,6 +72796,340 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72672
72796
|
type: Output
|
|
72673
72797
|
}] } });
|
|
72674
72798
|
|
|
72799
|
+
addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline });
|
|
72800
|
+
const CHAT_COMPOSER_I18N = {
|
|
72801
|
+
es: {
|
|
72802
|
+
placeholder: 'Escribe un mensaje',
|
|
72803
|
+
send: 'Enviar',
|
|
72804
|
+
attach: 'Adjuntar',
|
|
72805
|
+
emoji: 'Emoji',
|
|
72806
|
+
voice: 'Mensaje de voz',
|
|
72807
|
+
replyingTo: 'Respondiendo a',
|
|
72808
|
+
cancel: 'Cancelar',
|
|
72809
|
+
},
|
|
72810
|
+
en: {
|
|
72811
|
+
placeholder: 'Write a message',
|
|
72812
|
+
send: 'Send',
|
|
72813
|
+
attach: 'Attach',
|
|
72814
|
+
emoji: 'Emoji',
|
|
72815
|
+
voice: 'Voice message',
|
|
72816
|
+
replyingTo: 'Replying to',
|
|
72817
|
+
cancel: 'Cancel',
|
|
72818
|
+
},
|
|
72819
|
+
};
|
|
72820
|
+
const QUICK_EMOJIS = ['👍', '❤️', '😂', '🎉', '🙏', '🔥', '😮', '😢'];
|
|
72821
|
+
/**
|
|
72822
|
+
* val-chat-composer — barra de redacción de chat moderna (signal inputs).
|
|
72823
|
+
*
|
|
72824
|
+
* Pill con: adjuntar (+), textarea auto-grow, emoji (fila rápida) y mic/enviar
|
|
72825
|
+
* (mic cuando está vacío, enviar cuando hay texto — patrón WhatsApp/Telegram).
|
|
72826
|
+
* Muestra una barra de contexto "respondiendo a" cuando `replyingTo` está seteado.
|
|
72827
|
+
* `typing` se emite con debounce (máx. 1 vez cada 2s) para no spamear writes.
|
|
72828
|
+
*/
|
|
72829
|
+
class ChatComposerComponent {
|
|
72830
|
+
constructor() {
|
|
72831
|
+
this.i18n = inject(I18nService);
|
|
72832
|
+
this.placeholder = input('');
|
|
72833
|
+
this.disabled = input(false);
|
|
72834
|
+
this.maxLength = input(2000);
|
|
72835
|
+
this.replyingTo = input(null);
|
|
72836
|
+
this.showAttach = input(true);
|
|
72837
|
+
this.showEmoji = input(true);
|
|
72838
|
+
this.showMic = input(true);
|
|
72839
|
+
this.send = output();
|
|
72840
|
+
this.typing = output();
|
|
72841
|
+
this.attach = output();
|
|
72842
|
+
this.voice = output();
|
|
72843
|
+
this.cancelReply = output();
|
|
72844
|
+
this.body = signal('');
|
|
72845
|
+
this.emojiOpen = signal(false);
|
|
72846
|
+
this.quickEmojis = QUICK_EMOJIS;
|
|
72847
|
+
this.lastTypingEmit = 0;
|
|
72848
|
+
this.canSend = computed(() => {
|
|
72849
|
+
const t = this.body().trim();
|
|
72850
|
+
return t.length > 0 && t.length <= this.maxLength() && !this.disabled();
|
|
72851
|
+
});
|
|
72852
|
+
this.placeholderText = computed(() => {
|
|
72853
|
+
this.i18n.lang();
|
|
72854
|
+
return this.placeholder() || this.i18n.t('placeholder', 'ChatComposer');
|
|
72855
|
+
});
|
|
72856
|
+
this.i18n.registerDefaults('ChatComposer', CHAT_COMPOSER_I18N);
|
|
72857
|
+
}
|
|
72858
|
+
onInput(event) {
|
|
72859
|
+
const target = event.target;
|
|
72860
|
+
this.body.set(target.value ?? '');
|
|
72861
|
+
this.emitTypingDebounced();
|
|
72862
|
+
}
|
|
72863
|
+
onKeydown(event) {
|
|
72864
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
72865
|
+
event.preventDefault();
|
|
72866
|
+
this.onSend();
|
|
72867
|
+
}
|
|
72868
|
+
}
|
|
72869
|
+
onSend() {
|
|
72870
|
+
const trimmed = this.body().trim();
|
|
72871
|
+
if (!trimmed || trimmed.length > this.maxLength())
|
|
72872
|
+
return;
|
|
72873
|
+
this.send.emit(trimmed);
|
|
72874
|
+
this.body.set('');
|
|
72875
|
+
this.emojiOpen.set(false);
|
|
72876
|
+
}
|
|
72877
|
+
toggleEmoji() {
|
|
72878
|
+
this.emojiOpen.update(v => !v);
|
|
72879
|
+
}
|
|
72880
|
+
insertEmoji(emoji) {
|
|
72881
|
+
this.body.update(v => v + emoji);
|
|
72882
|
+
}
|
|
72883
|
+
onFile(event) {
|
|
72884
|
+
const input = event.target;
|
|
72885
|
+
const file = input.files?.[0];
|
|
72886
|
+
if (file)
|
|
72887
|
+
this.attach.emit(file);
|
|
72888
|
+
input.value = '';
|
|
72889
|
+
}
|
|
72890
|
+
/**
|
|
72891
|
+
* Emite typing como máximo 1 vez cada 2s. No usa Date.now() directo en plantillas
|
|
72892
|
+
* de workflow; aquí estamos en runtime de navegador, performance.now es seguro.
|
|
72893
|
+
*/
|
|
72894
|
+
emitTypingDebounced() {
|
|
72895
|
+
const now = performance.now();
|
|
72896
|
+
if (now - this.lastTypingEmit > 2000) {
|
|
72897
|
+
this.lastTypingEmit = now;
|
|
72898
|
+
this.typing.emit();
|
|
72899
|
+
}
|
|
72900
|
+
}
|
|
72901
|
+
t(key) {
|
|
72902
|
+
return this.i18n.t(key, 'ChatComposer');
|
|
72903
|
+
}
|
|
72904
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72905
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatComposerComponent, isStandalone: true, selector: "val-chat-composer", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, replyingTo: { classPropertyName: "replyingTo", publicName: "replyingTo", isSignal: true, isRequired: false, transformFunction: null }, showAttach: { classPropertyName: "showAttach", publicName: "showAttach", isSignal: true, isRequired: false, transformFunction: null }, showEmoji: { classPropertyName: "showEmoji", publicName: "showEmoji", isSignal: true, isRequired: false, transformFunction: null }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", attach: "attach", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
|
|
72906
|
+
<div class="composer">
|
|
72907
|
+
@if (replyingTo()) {
|
|
72908
|
+
<div class="reply-bar">
|
|
72909
|
+
<div class="reply-content">
|
|
72910
|
+
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
72911
|
+
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
72912
|
+
</div>
|
|
72913
|
+
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
72914
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
72915
|
+
</button>
|
|
72916
|
+
</div>
|
|
72917
|
+
}
|
|
72918
|
+
|
|
72919
|
+
@if (emojiOpen()) {
|
|
72920
|
+
<div class="emoji-row">
|
|
72921
|
+
@for (e of quickEmojis; track e) {
|
|
72922
|
+
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
72923
|
+
}
|
|
72924
|
+
</div>
|
|
72925
|
+
}
|
|
72926
|
+
|
|
72927
|
+
<div class="pill">
|
|
72928
|
+
@if (showAttach()) {
|
|
72929
|
+
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
72930
|
+
<ion-icon name="add-outline" aria-hidden="true" />
|
|
72931
|
+
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
72932
|
+
</label>
|
|
72933
|
+
}
|
|
72934
|
+
|
|
72935
|
+
<ion-textarea
|
|
72936
|
+
class="field"
|
|
72937
|
+
[placeholder]="placeholderText()"
|
|
72938
|
+
[disabled]="disabled()"
|
|
72939
|
+
[maxlength]="maxLength()"
|
|
72940
|
+
[autoGrow]="true"
|
|
72941
|
+
[rows]="1"
|
|
72942
|
+
[value]="body()"
|
|
72943
|
+
(ionInput)="onInput($event)"
|
|
72944
|
+
(keydown)="onKeydown($event)"
|
|
72945
|
+
></ion-textarea>
|
|
72946
|
+
|
|
72947
|
+
@if (showEmoji()) {
|
|
72948
|
+
<button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
|
|
72949
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72950
|
+
</button>
|
|
72951
|
+
}
|
|
72952
|
+
|
|
72953
|
+
@if (canSend()) {
|
|
72954
|
+
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
72955
|
+
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
72956
|
+
</button>
|
|
72957
|
+
} @else if (showMic()) {
|
|
72958
|
+
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
|
|
72959
|
+
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
72960
|
+
</button>
|
|
72961
|
+
}
|
|
72962
|
+
</div>
|
|
72963
|
+
</div>
|
|
72964
|
+
`, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
72965
|
+
}
|
|
72966
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
|
|
72967
|
+
type: Component,
|
|
72968
|
+
args: [{ selector: 'val-chat-composer', standalone: true, imports: [CommonModule, IonTextarea, IonIcon], template: `
|
|
72969
|
+
<div class="composer">
|
|
72970
|
+
@if (replyingTo()) {
|
|
72971
|
+
<div class="reply-bar">
|
|
72972
|
+
<div class="reply-content">
|
|
72973
|
+
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
72974
|
+
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
72975
|
+
</div>
|
|
72976
|
+
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
72977
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
72978
|
+
</button>
|
|
72979
|
+
</div>
|
|
72980
|
+
}
|
|
72981
|
+
|
|
72982
|
+
@if (emojiOpen()) {
|
|
72983
|
+
<div class="emoji-row">
|
|
72984
|
+
@for (e of quickEmojis; track e) {
|
|
72985
|
+
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
72986
|
+
}
|
|
72987
|
+
</div>
|
|
72988
|
+
}
|
|
72989
|
+
|
|
72990
|
+
<div class="pill">
|
|
72991
|
+
@if (showAttach()) {
|
|
72992
|
+
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
72993
|
+
<ion-icon name="add-outline" aria-hidden="true" />
|
|
72994
|
+
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
72995
|
+
</label>
|
|
72996
|
+
}
|
|
72997
|
+
|
|
72998
|
+
<ion-textarea
|
|
72999
|
+
class="field"
|
|
73000
|
+
[placeholder]="placeholderText()"
|
|
73001
|
+
[disabled]="disabled()"
|
|
73002
|
+
[maxlength]="maxLength()"
|
|
73003
|
+
[autoGrow]="true"
|
|
73004
|
+
[rows]="1"
|
|
73005
|
+
[value]="body()"
|
|
73006
|
+
(ionInput)="onInput($event)"
|
|
73007
|
+
(keydown)="onKeydown($event)"
|
|
73008
|
+
></ion-textarea>
|
|
73009
|
+
|
|
73010
|
+
@if (showEmoji()) {
|
|
73011
|
+
<button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
|
|
73012
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
73013
|
+
</button>
|
|
73014
|
+
}
|
|
73015
|
+
|
|
73016
|
+
@if (canSend()) {
|
|
73017
|
+
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73018
|
+
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73019
|
+
</button>
|
|
73020
|
+
} @else if (showMic()) {
|
|
73021
|
+
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
|
|
73022
|
+
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73023
|
+
</button>
|
|
73024
|
+
}
|
|
73025
|
+
</div>
|
|
73026
|
+
</div>
|
|
73027
|
+
`, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"] }]
|
|
73028
|
+
}], ctorParameters: () => [] });
|
|
73029
|
+
|
|
73030
|
+
/**
|
|
73031
|
+
* val-conversation-list-item — fila de lista de conversaciones estilo
|
|
73032
|
+
* Telegram/WhatsApp: avatar (imagen o iniciales), nombre + hora relativa,
|
|
73033
|
+
* preview del último mensaje + badge de no-leídos. Signal inputs, dark-safe.
|
|
73034
|
+
*
|
|
73035
|
+
* @example
|
|
73036
|
+
* <val-conversation-list-item
|
|
73037
|
+
* [name]="conv.name"
|
|
73038
|
+
* [lastMessage]="conv.lastMsgPreview"
|
|
73039
|
+
* [timestamp]="conv.lastMsgAt"
|
|
73040
|
+
* [unreadCount]="conv.unread"
|
|
73041
|
+
* (select)="open(conv.convId)" />
|
|
73042
|
+
*/
|
|
73043
|
+
class ConversationListItemComponent {
|
|
73044
|
+
constructor() {
|
|
73045
|
+
this.i18n = inject(I18nService);
|
|
73046
|
+
this.name = input('');
|
|
73047
|
+
this.avatarUrl = input(undefined);
|
|
73048
|
+
this.lastMessage = input('');
|
|
73049
|
+
this.timestamp = input(undefined);
|
|
73050
|
+
this.unreadCount = input(0);
|
|
73051
|
+
this.locale = input('es-CL');
|
|
73052
|
+
this.select = output();
|
|
73053
|
+
this.initials = computed(() => {
|
|
73054
|
+
const name = this.name().trim();
|
|
73055
|
+
if (!name)
|
|
73056
|
+
return '?';
|
|
73057
|
+
const parts = name.split(/\s+/);
|
|
73058
|
+
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
73059
|
+
});
|
|
73060
|
+
this.timeLabel = computed(() => formatRelativeTime(this.timestamp(), this.locale(), {
|
|
73061
|
+
today: '',
|
|
73062
|
+
yesterday: this.i18n.t('yesterday', '_global'),
|
|
73063
|
+
}));
|
|
73064
|
+
this.i18n.registerDefaults('ConversationListItem', {
|
|
73065
|
+
es: { empty: 'Sin mensajes' },
|
|
73066
|
+
en: { empty: 'No messages' },
|
|
73067
|
+
});
|
|
73068
|
+
}
|
|
73069
|
+
emptyText() {
|
|
73070
|
+
this.i18n.lang();
|
|
73071
|
+
return this.i18n.t('empty', 'ConversationListItem');
|
|
73072
|
+
}
|
|
73073
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73074
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ConversationListItemComponent, isStandalone: true, selector: "val-conversation-list-item", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, lastMessage: { classPropertyName: "lastMessage", publicName: "lastMessage", isSignal: true, isRequired: false, transformFunction: null }, timestamp: { classPropertyName: "timestamp", publicName: "timestamp", isSignal: true, isRequired: false, transformFunction: null }, unreadCount: { classPropertyName: "unreadCount", publicName: "unreadCount", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select" }, ngImport: i0, template: `
|
|
73075
|
+
<button class="item" (click)="select.emit()">
|
|
73076
|
+
<div class="avatar">
|
|
73077
|
+
@if (avatarUrl()) {
|
|
73078
|
+
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73079
|
+
} @else {
|
|
73080
|
+
<span>{{ initials() }}</span>
|
|
73081
|
+
}
|
|
73082
|
+
</div>
|
|
73083
|
+
<div class="content">
|
|
73084
|
+
<div class="top">
|
|
73085
|
+
<span class="name">{{ name() }}</span>
|
|
73086
|
+
@if (timeLabel()) {
|
|
73087
|
+
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73088
|
+
}
|
|
73089
|
+
</div>
|
|
73090
|
+
<div class="bottom">
|
|
73091
|
+
<span class="preview" [class.muted]="!lastMessage()">
|
|
73092
|
+
{{ lastMessage() || emptyText() }}
|
|
73093
|
+
</span>
|
|
73094
|
+
@if (unreadCount() > 0) {
|
|
73095
|
+
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73096
|
+
}
|
|
73097
|
+
</div>
|
|
73098
|
+
</div>
|
|
73099
|
+
</button>
|
|
73100
|
+
`, isInline: true, styles: [":host{display:block}.item{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;border:none;background:transparent;cursor:pointer;text-align:left;border-radius:12px;transition:background .12s ease}.item:hover{background:var(--ion-color-step-50, rgba(127, 127, 127, .06))}.avatar{width:48px;height:48px;flex-shrink:0;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-weight:700;font-size:1rem}.avatar img{width:100%;height:100%;object-fit:cover}.content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.top,.bottom{display:flex;align-items:center;gap:8px}.name{flex:1;min-width:0;font-weight:600;font-size:.9375rem;color:var(--ion-text-color, #000);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.time{flex-shrink:0;font-size:.75rem;color:var(--ion-color-medium, #92949c)}.time.unread{color:var(--ion-color-primary);font-weight:600}.preview{flex:1;min-width:0;font-size:.875rem;color:var(--ion-color-medium, #92949c);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview.muted{font-style:italic;opacity:.7}.badge{flex-shrink:0;min-width:20px;height:20px;padding:0 6px;border-radius:10px;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);font-size:.6875rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
|
|
73101
|
+
}
|
|
73102
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, decorators: [{
|
|
73103
|
+
type: Component,
|
|
73104
|
+
args: [{ selector: 'val-conversation-list-item', standalone: true, imports: [CommonModule], template: `
|
|
73105
|
+
<button class="item" (click)="select.emit()">
|
|
73106
|
+
<div class="avatar">
|
|
73107
|
+
@if (avatarUrl()) {
|
|
73108
|
+
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73109
|
+
} @else {
|
|
73110
|
+
<span>{{ initials() }}</span>
|
|
73111
|
+
}
|
|
73112
|
+
</div>
|
|
73113
|
+
<div class="content">
|
|
73114
|
+
<div class="top">
|
|
73115
|
+
<span class="name">{{ name() }}</span>
|
|
73116
|
+
@if (timeLabel()) {
|
|
73117
|
+
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73118
|
+
}
|
|
73119
|
+
</div>
|
|
73120
|
+
<div class="bottom">
|
|
73121
|
+
<span class="preview" [class.muted]="!lastMessage()">
|
|
73122
|
+
{{ lastMessage() || emptyText() }}
|
|
73123
|
+
</span>
|
|
73124
|
+
@if (unreadCount() > 0) {
|
|
73125
|
+
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73126
|
+
}
|
|
73127
|
+
</div>
|
|
73128
|
+
</div>
|
|
73129
|
+
</button>
|
|
73130
|
+
`, styles: [":host{display:block}.item{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;border:none;background:transparent;cursor:pointer;text-align:left;border-radius:12px;transition:background .12s ease}.item:hover{background:var(--ion-color-step-50, rgba(127, 127, 127, .06))}.avatar{width:48px;height:48px;flex-shrink:0;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-weight:700;font-size:1rem}.avatar img{width:100%;height:100%;object-fit:cover}.content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.top,.bottom{display:flex;align-items:center;gap:8px}.name{flex:1;min-width:0;font-weight:600;font-size:.9375rem;color:var(--ion-text-color, #000);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.time{flex-shrink:0;font-size:.75rem;color:var(--ion-color-medium, #92949c)}.time.unread{color:var(--ion-color-primary);font-weight:600}.preview{flex:1;min-width:0;font-size:.875rem;color:var(--ion-color-medium, #92949c);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview.muted{font-style:italic;opacity:.7}.badge{flex-shrink:0;min-width:20px;height:20px;padding:0 6px;border-radius:10px;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);font-size:.6875rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center}\n"] }]
|
|
73131
|
+
}], ctorParameters: () => [] });
|
|
73132
|
+
|
|
72675
73133
|
const TYPING_I18N = {
|
|
72676
73134
|
es: {
|
|
72677
73135
|
one: '{name} esta escribiendo',
|
|
@@ -72687,8 +73145,9 @@ const TYPING_I18N = {
|
|
|
72687
73145
|
/**
|
|
72688
73146
|
* val-typing-indicator
|
|
72689
73147
|
*
|
|
72690
|
-
* Muestra quienes estan escribiendo
|
|
72691
|
-
*
|
|
73148
|
+
* Muestra quienes estan escribiendo con animacion de tres puntos. Se oculta
|
|
73149
|
+
* si `typingUsers` esta vacio. Usa signal input para que `typingText` (computed)
|
|
73150
|
+
* recompute al cambiar la lista.
|
|
72692
73151
|
*
|
|
72693
73152
|
* @example
|
|
72694
73153
|
* <val-typing-indicator [typingUsers]="['Maria', 'Carlos']" />
|
|
@@ -72696,10 +73155,10 @@ const TYPING_I18N = {
|
|
|
72696
73155
|
class TypingIndicatorComponent {
|
|
72697
73156
|
constructor() {
|
|
72698
73157
|
this.i18n = inject(I18nService);
|
|
72699
|
-
this.typingUsers = [];
|
|
73158
|
+
this.typingUsers = input([]);
|
|
72700
73159
|
this.typingText = computed(() => {
|
|
72701
73160
|
this.i18n.lang();
|
|
72702
|
-
const users = this.typingUsers
|
|
73161
|
+
const users = this.typingUsers();
|
|
72703
73162
|
if (users.length === 0)
|
|
72704
73163
|
return '';
|
|
72705
73164
|
if (users.length === 1) {
|
|
@@ -72713,8 +73172,8 @@ class TypingIndicatorComponent {
|
|
|
72713
73172
|
this.i18n.registerDefaults('TypingIndicator', TYPING_I18N);
|
|
72714
73173
|
}
|
|
72715
73174
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72716
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: TypingIndicatorComponent, isStandalone: true, selector: "val-typing-indicator", inputs: { typingUsers: "typingUsers" }, ngImport: i0, template: `
|
|
72717
|
-
@if (typingUsers
|
|
73175
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: TypingIndicatorComponent, isStandalone: true, selector: "val-typing-indicator", inputs: { typingUsers: { classPropertyName: "typingUsers", publicName: "typingUsers", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
73176
|
+
@if (typingUsers().length > 0) {
|
|
72718
73177
|
<div class="typing-indicator">
|
|
72719
73178
|
<div class="dots">
|
|
72720
73179
|
<span></span>
|
|
@@ -72729,7 +73188,7 @@ class TypingIndicatorComponent {
|
|
|
72729
73188
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, decorators: [{
|
|
72730
73189
|
type: Component,
|
|
72731
73190
|
args: [{ selector: 'val-typing-indicator', standalone: true, imports: [CommonModule], template: `
|
|
72732
|
-
@if (typingUsers
|
|
73191
|
+
@if (typingUsers().length > 0) {
|
|
72733
73192
|
<div class="typing-indicator">
|
|
72734
73193
|
<div class="dots">
|
|
72735
73194
|
<span></span>
|
|
@@ -72740,245 +73199,280 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72740
73199
|
</div>
|
|
72741
73200
|
}
|
|
72742
73201
|
`, styles: [".typing-indicator{display:flex;align-items:center;gap:8px;padding:4px 12px;min-height:28px}.typing-text{font-size:.8125rem;color:var(--ion-color-medium, #92949c);font-style:italic}.dots{display:flex;align-items:center;gap:3px}.dots span{display:inline-block;width:5px;height:5px;border-radius:50%;background:var(--ion-color-medium, #92949c);animation:typing-dot 1.2s infinite ease-in-out}.dots span:nth-child(1){animation-delay:0s}.dots span:nth-child(2){animation-delay:.2s}.dots span:nth-child(3){animation-delay:.4s}@keyframes typing-dot{0%,60%,to{transform:translateY(0);opacity:.4}30%{transform:translateY(-4px);opacity:1}}\n"] }]
|
|
72743
|
-
}], ctorParameters: () => []
|
|
72744
|
-
type: Input
|
|
72745
|
-
}] } });
|
|
73202
|
+
}], ctorParameters: () => [] });
|
|
72746
73203
|
|
|
72747
|
-
addIcons({ chatbubblesOutline });
|
|
73204
|
+
addIcons({ arrowDownOutline, chatbubblesOutline });
|
|
72748
73205
|
const CHAT_WINDOW_I18N = {
|
|
72749
73206
|
es: {
|
|
72750
73207
|
conversationClosed: 'Esta conversacion esta cerrada',
|
|
72751
73208
|
loading: 'Cargando mensajes...',
|
|
72752
|
-
noMessages: '
|
|
73209
|
+
noMessages: 'Aun no hay mensajes. Escribe el primero.',
|
|
73210
|
+
newMessages: 'Mensajes nuevos',
|
|
72753
73211
|
},
|
|
72754
73212
|
en: {
|
|
72755
73213
|
conversationClosed: 'This conversation is closed',
|
|
72756
73214
|
loading: 'Loading messages...',
|
|
72757
|
-
noMessages: 'No messages yet',
|
|
73215
|
+
noMessages: 'No messages yet. Send the first one.',
|
|
73216
|
+
newMessages: 'New messages',
|
|
72758
73217
|
},
|
|
72759
73218
|
};
|
|
72760
73219
|
/**
|
|
72761
|
-
* val-chat-window
|
|
73220
|
+
* val-chat-window — ventana de chat completa (signal inputs).
|
|
72762
73221
|
*
|
|
72763
|
-
*
|
|
72764
|
-
*
|
|
72765
|
-
*
|
|
72766
|
-
*
|
|
73222
|
+
* Agrupa mensajes consecutivos del mismo emisor y por día (separadores sticky),
|
|
73223
|
+
* muestra avatar/nombre solo en los bordes del grupo, scroll-to-bottom inteligente
|
|
73224
|
+
* (auto-scroll si estás abajo, pill "mensajes nuevos" si estás leyendo arriba),
|
|
73225
|
+
* composer moderno con contexto de respuesta, y typing indicator.
|
|
72767
73226
|
*
|
|
72768
|
-
*
|
|
72769
|
-
*
|
|
72770
|
-
*
|
|
72771
|
-
* [messages]="messages()"
|
|
72772
|
-
* [currentUserId]="userId"
|
|
72773
|
-
* [isOpen]="conv.status === 'open'"
|
|
72774
|
-
* [typingUsers]="typingUsers()"
|
|
72775
|
-
* (sendMessage)="onSend($event)"
|
|
72776
|
-
* (reactionClick)="onReaction($event)"
|
|
72777
|
-
* />
|
|
73227
|
+
* API pública estable (consumida por thread-panel y showcase): inputs convId,
|
|
73228
|
+
* messages, currentUserId, isOpen, typingUsers, isLoading; outputs sendMessage,
|
|
73229
|
+
* loadMore, reactionClick, deleteMessage, replyTo (+ editMessage/attach/voice nuevos).
|
|
72778
73230
|
*/
|
|
72779
73231
|
class ChatWindowComponent {
|
|
72780
73232
|
constructor() {
|
|
72781
73233
|
this.i18n = inject(I18nService);
|
|
72782
|
-
this.convId = '';
|
|
72783
|
-
this.messages = [];
|
|
72784
|
-
this.
|
|
72785
|
-
this.
|
|
72786
|
-
this.
|
|
72787
|
-
this.
|
|
72788
|
-
this.
|
|
72789
|
-
this.
|
|
72790
|
-
this.
|
|
72791
|
-
this.
|
|
72792
|
-
this.
|
|
72793
|
-
this.
|
|
72794
|
-
this.
|
|
72795
|
-
this.
|
|
72796
|
-
this.
|
|
72797
|
-
|
|
73234
|
+
this.convId = input('');
|
|
73235
|
+
this.messages = input([]);
|
|
73236
|
+
this.currentUserId = input('');
|
|
73237
|
+
this.isOpen = input(true);
|
|
73238
|
+
this.typingUsers = input([]);
|
|
73239
|
+
this.isLoading = input(false);
|
|
73240
|
+
this.sendMessage = output();
|
|
73241
|
+
this.loadMore = output();
|
|
73242
|
+
this.reactionClick = output();
|
|
73243
|
+
this.deleteMessage = output();
|
|
73244
|
+
this.replyTo = output();
|
|
73245
|
+
this.editMessage = output();
|
|
73246
|
+
this.typing = output();
|
|
73247
|
+
this.attach = output();
|
|
73248
|
+
this.voice = output();
|
|
73249
|
+
this.msgsEl = viewChild('msgs');
|
|
73250
|
+
this.replyingTo = signal(null);
|
|
73251
|
+
this.atBottom = signal(true);
|
|
73252
|
+
this.hasNew = signal(false);
|
|
73253
|
+
this.locale = computed(() => (this.i18n.lang() === 'en' ? 'en-US' : 'es-CL'));
|
|
73254
|
+
this.rows = computed(() => {
|
|
73255
|
+
const msgs = this.messages();
|
|
72798
73256
|
if (msgs.length === 0)
|
|
72799
73257
|
return [];
|
|
72800
|
-
const
|
|
72801
|
-
|
|
72802
|
-
|
|
72803
|
-
|
|
72804
|
-
|
|
72805
|
-
|
|
72806
|
-
|
|
72807
|
-
|
|
72808
|
-
|
|
72809
|
-
|
|
72810
|
-
|
|
72811
|
-
|
|
72812
|
-
|
|
72813
|
-
|
|
73258
|
+
const labels = {
|
|
73259
|
+
today: this.i18n.t('today', '_global'),
|
|
73260
|
+
yesterday: this.i18n.t('yesterday', '_global'),
|
|
73261
|
+
};
|
|
73262
|
+
const out = [];
|
|
73263
|
+
let prevDay = '';
|
|
73264
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
73265
|
+
const m = msgs[i];
|
|
73266
|
+
const d = m.createdAt instanceof Date ? m.createdAt : new Date(m.createdAt);
|
|
73267
|
+
const day = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
73268
|
+
const newDay = day !== prevDay;
|
|
73269
|
+
if (newDay) {
|
|
73270
|
+
out.push({ kind: 'date', id: 'date-' + day, label: formatDateSeparator(d, this.locale(), labels) });
|
|
73271
|
+
prevDay = day;
|
|
72814
73272
|
}
|
|
73273
|
+
const prev = msgs[i - 1];
|
|
73274
|
+
const next = msgs[i + 1];
|
|
73275
|
+
const firstInGroup = newDay || !prev || prev.senderId !== m.senderId;
|
|
73276
|
+
const nextDay = next
|
|
73277
|
+
? `${new Date(next.createdAt).getFullYear()}-${new Date(next.createdAt).getMonth()}-${new Date(next.createdAt).getDate()}`
|
|
73278
|
+
: '';
|
|
73279
|
+
const lastInGroup = !next || next.senderId !== m.senderId || nextDay !== day;
|
|
73280
|
+
out.push({
|
|
73281
|
+
kind: 'msg',
|
|
73282
|
+
id: m.msgId,
|
|
73283
|
+
msg: m,
|
|
73284
|
+
showName: firstInGroup && !m.isMine,
|
|
73285
|
+
showAvatar: lastInGroup && !m.isMine,
|
|
73286
|
+
tail: lastInGroup,
|
|
73287
|
+
});
|
|
72815
73288
|
}
|
|
72816
|
-
|
|
72817
|
-
groups.push({ dateLabel: currentLabel, messages: currentGroup });
|
|
72818
|
-
}
|
|
72819
|
-
return groups;
|
|
72820
|
-
});
|
|
72821
|
-
this.closedText = computed(() => {
|
|
72822
|
-
this.i18n.lang();
|
|
72823
|
-
return this.i18n.t('conversationClosed', 'ChatWindow');
|
|
72824
|
-
});
|
|
72825
|
-
this.loadingText = computed(() => {
|
|
72826
|
-
this.i18n.lang();
|
|
72827
|
-
return this.i18n.t('loading', 'ChatWindow');
|
|
73289
|
+
return out;
|
|
72828
73290
|
});
|
|
72829
73291
|
this.i18n.registerDefaults('ChatWindow', CHAT_WINDOW_I18N);
|
|
72830
|
-
|
|
72831
|
-
|
|
72832
|
-
|
|
72833
|
-
|
|
72834
|
-
|
|
72835
|
-
|
|
72836
|
-
|
|
73292
|
+
// Auto-scroll al fondo cuando llegan mensajes y el usuario ya estaba abajo
|
|
73293
|
+
// (o el último mensaje es propio). Si está leyendo arriba, marca "nuevos".
|
|
73294
|
+
let prevCount = 0;
|
|
73295
|
+
effect(() => {
|
|
73296
|
+
const rows = this.rows();
|
|
73297
|
+
const count = rows.length;
|
|
73298
|
+
if (count === prevCount)
|
|
73299
|
+
return;
|
|
73300
|
+
const grew = count > prevCount;
|
|
73301
|
+
prevCount = count;
|
|
73302
|
+
const last = this.messages()[this.messages().length - 1];
|
|
73303
|
+
const mine = last?.isMine ?? false;
|
|
73304
|
+
if (this.atBottom() || mine) {
|
|
73305
|
+
setTimeout(() => this.scrollToBottom(false), 0);
|
|
72837
73306
|
}
|
|
72838
|
-
|
|
72839
|
-
|
|
72840
|
-
|
|
72841
|
-
|
|
72842
|
-
this.scrollToBottom();
|
|
72843
|
-
this.shouldScrollToBottom = false;
|
|
72844
|
-
}
|
|
73307
|
+
else if (grew) {
|
|
73308
|
+
this.hasNew.set(true);
|
|
73309
|
+
}
|
|
73310
|
+
});
|
|
72845
73311
|
}
|
|
72846
|
-
|
|
72847
|
-
|
|
73312
|
+
onScroll() {
|
|
73313
|
+
const el = this.msgsEl()?.nativeElement;
|
|
73314
|
+
if (!el)
|
|
73315
|
+
return;
|
|
73316
|
+
const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
73317
|
+
this.atBottom.set(nearBottom);
|
|
73318
|
+
if (nearBottom)
|
|
73319
|
+
this.hasNew.set(false);
|
|
73320
|
+
if (el.scrollTop < 40)
|
|
73321
|
+
this.loadMore.emit();
|
|
72848
73322
|
}
|
|
72849
|
-
|
|
72850
|
-
|
|
73323
|
+
scrollToBottom(smooth) {
|
|
73324
|
+
const el = this.msgsEl()?.nativeElement;
|
|
73325
|
+
if (!el)
|
|
73326
|
+
return;
|
|
73327
|
+
el.scrollTo({ top: el.scrollHeight, behavior: smooth ? 'smooth' : 'auto' });
|
|
73328
|
+
this.atBottom.set(true);
|
|
73329
|
+
this.hasNew.set(false);
|
|
72851
73330
|
}
|
|
72852
|
-
|
|
72853
|
-
|
|
73331
|
+
onSend(body) {
|
|
73332
|
+
this.sendMessage.emit(body);
|
|
73333
|
+
this.replyingTo.set(null);
|
|
72854
73334
|
}
|
|
72855
|
-
|
|
72856
|
-
|
|
72857
|
-
|
|
72858
|
-
|
|
73335
|
+
onAction(event) {
|
|
73336
|
+
switch (event.type) {
|
|
73337
|
+
case 'reply': {
|
|
73338
|
+
const m = this.messages().find(x => x.msgId === event.msgId);
|
|
73339
|
+
if (m) {
|
|
73340
|
+
this.replyingTo.set({ msgId: m.msgId, senderName: m.senderName, body: m.body });
|
|
73341
|
+
}
|
|
73342
|
+
this.replyTo.emit(event.msgId);
|
|
73343
|
+
break;
|
|
73344
|
+
}
|
|
73345
|
+
case 'edit':
|
|
73346
|
+
this.editMessage.emit(event.msgId);
|
|
73347
|
+
break;
|
|
73348
|
+
case 'delete':
|
|
73349
|
+
this.deleteMessage.emit(event.msgId);
|
|
73350
|
+
break;
|
|
73351
|
+
case 'react':
|
|
73352
|
+
this.reactionClick.emit({ msgId: event.msgId, token: event.token ?? '👍' });
|
|
73353
|
+
break;
|
|
72859
73354
|
}
|
|
72860
73355
|
}
|
|
72861
|
-
|
|
72862
|
-
|
|
72863
|
-
return '';
|
|
72864
|
-
const d = date instanceof Date ? date : new Date(date);
|
|
72865
|
-
const now = new Date();
|
|
72866
|
-
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
72867
|
-
const msgDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
72868
|
-
const diffMs = today.getTime() - msgDay.getTime();
|
|
72869
|
-
const diffDays = Math.round(diffMs / 86400000);
|
|
72870
|
-
if (diffDays === 0)
|
|
72871
|
-
return this.i18n.t('today', '_global') || 'Hoy';
|
|
72872
|
-
if (diffDays === 1)
|
|
72873
|
-
return this.i18n.t('yesterday', '_global') || 'Ayer';
|
|
72874
|
-
return d.toLocaleDateString([], { day: '2-digit', month: 'short', year: 'numeric' });
|
|
73356
|
+
t(key) {
|
|
73357
|
+
return this.i18n.t(key, 'ChatWindow');
|
|
72875
73358
|
}
|
|
72876
73359
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72877
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatWindowComponent, isStandalone: true, selector: "val-chat-window", inputs: { convId: "convId", messages: "messages",
|
|
72878
|
-
<div class="chat
|
|
72879
|
-
<div class="messages
|
|
72880
|
-
@if (isLoading) {
|
|
72881
|
-
<div class="
|
|
72882
|
-
<
|
|
72883
|
-
|
|
73360
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatWindowComponent, isStandalone: true, selector: "val-chat-window", inputs: { convId: { classPropertyName: "convId", publicName: "convId", isSignal: true, isRequired: false, transformFunction: null }, messages: { classPropertyName: "messages", publicName: "messages", isSignal: true, isRequired: false, transformFunction: null }, currentUserId: { classPropertyName: "currentUserId", publicName: "currentUserId", isSignal: true, isRequired: false, transformFunction: null }, isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, typingUsers: { classPropertyName: "typingUsers", publicName: "typingUsers", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sendMessage: "sendMessage", loadMore: "loadMore", reactionClick: "reactionClick", deleteMessage: "deleteMessage", replyTo: "replyTo", editMessage: "editMessage", typing: "typing", attach: "attach", voice: "voice" }, viewQueries: [{ propertyName: "msgsEl", first: true, predicate: ["msgs"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
73361
|
+
<div class="chat">
|
|
73362
|
+
<div #msgs class="messages" (scroll)="onScroll()">
|
|
73363
|
+
@if (isLoading()) {
|
|
73364
|
+
<div class="state">
|
|
73365
|
+
<span>{{ t('loading') }}</span>
|
|
73366
|
+
</div>
|
|
73367
|
+
} @else if (rows().length === 0) {
|
|
73368
|
+
<div class="state empty">
|
|
73369
|
+
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73370
|
+
<span>{{ t('noMessages') }}</span>
|
|
72884
73371
|
</div>
|
|
72885
73372
|
} @else {
|
|
72886
|
-
@for (
|
|
72887
|
-
|
|
72888
|
-
<div class="date-
|
|
72889
|
-
|
|
72890
|
-
|
|
72891
|
-
|
|
72892
|
-
@for (msg of group.messages; track msg.msgId) {
|
|
73373
|
+
@for (row of rows(); track row.id) {
|
|
73374
|
+
@if (row.kind === 'date') {
|
|
73375
|
+
<div class="date-sep">
|
|
73376
|
+
<span>{{ row.label }}</span>
|
|
73377
|
+
</div>
|
|
73378
|
+
} @else {
|
|
72893
73379
|
<val-message-bubble
|
|
72894
|
-
[
|
|
72895
|
-
|
|
72896
|
-
|
|
72897
|
-
|
|
72898
|
-
|
|
73380
|
+
[msg]="row.msg"
|
|
73381
|
+
[showName]="row.showName"
|
|
73382
|
+
[showAvatar]="row.showAvatar"
|
|
73383
|
+
[tail]="row.tail"
|
|
73384
|
+
[locale]="locale()"
|
|
73385
|
+
(action)="onAction($event)"
|
|
72899
73386
|
/>
|
|
72900
73387
|
}
|
|
72901
73388
|
}
|
|
72902
73389
|
}
|
|
73390
|
+
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
72903
73391
|
</div>
|
|
72904
73392
|
|
|
72905
|
-
|
|
73393
|
+
@if (!atBottom()) {
|
|
73394
|
+
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73395
|
+
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73396
|
+
@if (hasNew()) {
|
|
73397
|
+
<span class="dot"></span>
|
|
73398
|
+
}
|
|
73399
|
+
</button>
|
|
73400
|
+
}
|
|
72906
73401
|
|
|
72907
|
-
@if (
|
|
72908
|
-
<
|
|
73402
|
+
@if (isOpen()) {
|
|
73403
|
+
<val-chat-composer
|
|
73404
|
+
[replyingTo]="replyingTo()"
|
|
73405
|
+
(send)="onSend($event)"
|
|
73406
|
+
(typing)="typing.emit()"
|
|
73407
|
+
(attach)="attach.emit($event)"
|
|
73408
|
+
(voice)="voice.emit()"
|
|
73409
|
+
(cancelReply)="replyingTo.set(null)"
|
|
73410
|
+
/>
|
|
72909
73411
|
} @else {
|
|
72910
|
-
<
|
|
73412
|
+
<div class="closed-banner">{{ t('conversationClosed') }}</div>
|
|
72911
73413
|
}
|
|
72912
73414
|
</div>
|
|
72913
|
-
`, isInline: true, styles: [".chat
|
|
73415
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.chat{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}.messages{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}.state{margin:auto;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ion-color-medium, #92949c);font-size:.9375rem;text-align:center;padding:24px}.state.empty ion-icon{font-size:2.5rem;opacity:.5}.date-sep{position:sticky;top:4px;z-index:1;display:flex;justify-content:center;margin:8px 0}.date-sep span{font-size:.75rem;color:var(--ion-color-medium, #92949c);background:var(--ion-color-step-100, rgba(127, 127, 127, .14));padding:3px 12px;border-radius:999px;backdrop-filter:blur(4px)}.scroll-down{position:absolute;right:14px;bottom:84px;width:40px;height:40px;border-radius:50%;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium, #92949c);box-shadow:0 2px 10px #00000026;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1.25rem;z-index:3}.scroll-down .dot{position:absolute;top:0;right:0;width:12px;height:12px;border-radius:50%;background:var(--ion-color-primary);border:2px solid var(--ion-card-background, #fff)}.closed-banner{text-align:center;padding:14px;color:var(--ion-color-medium, #92949c);font-size:.875rem;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}\n"], dependencies: [{ 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: MessageBubbleComponent, selector: "val-message-bubble", inputs: ["msg", "showAvatar", "showName", "tail", "locale"], outputs: ["action"] }, { kind: "component", type: ChatComposerComponent, selector: "val-chat-composer", inputs: ["placeholder", "disabled", "maxLength", "replyingTo", "showAttach", "showEmoji", "showMic"], outputs: ["send", "typing", "attach", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
|
|
72914
73416
|
}
|
|
72915
73417
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
|
|
72916
73418
|
type: Component,
|
|
72917
|
-
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon,
|
|
72918
|
-
<div class="chat
|
|
72919
|
-
<div class="messages
|
|
72920
|
-
@if (isLoading) {
|
|
72921
|
-
<div class="
|
|
72922
|
-
<
|
|
72923
|
-
|
|
73419
|
+
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon, MessageBubbleComponent, ChatComposerComponent, TypingIndicatorComponent], template: `
|
|
73420
|
+
<div class="chat">
|
|
73421
|
+
<div #msgs class="messages" (scroll)="onScroll()">
|
|
73422
|
+
@if (isLoading()) {
|
|
73423
|
+
<div class="state">
|
|
73424
|
+
<span>{{ t('loading') }}</span>
|
|
73425
|
+
</div>
|
|
73426
|
+
} @else if (rows().length === 0) {
|
|
73427
|
+
<div class="state empty">
|
|
73428
|
+
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73429
|
+
<span>{{ t('noMessages') }}</span>
|
|
72924
73430
|
</div>
|
|
72925
73431
|
} @else {
|
|
72926
|
-
@for (
|
|
72927
|
-
|
|
72928
|
-
<div class="date-
|
|
72929
|
-
|
|
72930
|
-
|
|
72931
|
-
|
|
72932
|
-
@for (msg of group.messages; track msg.msgId) {
|
|
73432
|
+
@for (row of rows(); track row.id) {
|
|
73433
|
+
@if (row.kind === 'date') {
|
|
73434
|
+
<div class="date-sep">
|
|
73435
|
+
<span>{{ row.label }}</span>
|
|
73436
|
+
</div>
|
|
73437
|
+
} @else {
|
|
72933
73438
|
<val-message-bubble
|
|
72934
|
-
[
|
|
72935
|
-
|
|
72936
|
-
|
|
72937
|
-
|
|
72938
|
-
|
|
73439
|
+
[msg]="row.msg"
|
|
73440
|
+
[showName]="row.showName"
|
|
73441
|
+
[showAvatar]="row.showAvatar"
|
|
73442
|
+
[tail]="row.tail"
|
|
73443
|
+
[locale]="locale()"
|
|
73444
|
+
(action)="onAction($event)"
|
|
72939
73445
|
/>
|
|
72940
73446
|
}
|
|
72941
73447
|
}
|
|
72942
73448
|
}
|
|
73449
|
+
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
72943
73450
|
</div>
|
|
72944
73451
|
|
|
72945
|
-
|
|
73452
|
+
@if (!atBottom()) {
|
|
73453
|
+
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73454
|
+
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73455
|
+
@if (hasNew()) {
|
|
73456
|
+
<span class="dot"></span>
|
|
73457
|
+
}
|
|
73458
|
+
</button>
|
|
73459
|
+
}
|
|
72946
73460
|
|
|
72947
|
-
@if (
|
|
72948
|
-
<
|
|
73461
|
+
@if (isOpen()) {
|
|
73462
|
+
<val-chat-composer
|
|
73463
|
+
[replyingTo]="replyingTo()"
|
|
73464
|
+
(send)="onSend($event)"
|
|
73465
|
+
(typing)="typing.emit()"
|
|
73466
|
+
(attach)="attach.emit($event)"
|
|
73467
|
+
(voice)="voice.emit()"
|
|
73468
|
+
(cancelReply)="replyingTo.set(null)"
|
|
73469
|
+
/>
|
|
72949
73470
|
} @else {
|
|
72950
|
-
<
|
|
73471
|
+
<div class="closed-banner">{{ t('conversationClosed') }}</div>
|
|
72951
73472
|
}
|
|
72952
73473
|
</div>
|
|
72953
|
-
`, styles: [".chat
|
|
72954
|
-
}], ctorParameters: () => []
|
|
72955
|
-
type: Input
|
|
72956
|
-
}], messages: [{
|
|
72957
|
-
type: Input
|
|
72958
|
-
}], participants: [{
|
|
72959
|
-
type: Input
|
|
72960
|
-
}], currentUserId: [{
|
|
72961
|
-
type: Input
|
|
72962
|
-
}], isOpen: [{
|
|
72963
|
-
type: Input
|
|
72964
|
-
}], typingUsers: [{
|
|
72965
|
-
type: Input
|
|
72966
|
-
}], isLoading: [{
|
|
72967
|
-
type: Input
|
|
72968
|
-
}], sendMessage: [{
|
|
72969
|
-
type: Output
|
|
72970
|
-
}], loadMore: [{
|
|
72971
|
-
type: Output
|
|
72972
|
-
}], reactionClick: [{
|
|
72973
|
-
type: Output
|
|
72974
|
-
}], deleteMessage: [{
|
|
72975
|
-
type: Output
|
|
72976
|
-
}], replyTo: [{
|
|
72977
|
-
type: Output
|
|
72978
|
-
}], messagesListRef: [{
|
|
72979
|
-
type: ViewChild,
|
|
72980
|
-
args: ['messagesList']
|
|
72981
|
-
}] } });
|
|
73474
|
+
`, styles: [":host{display:block;height:100%}.chat{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}.messages{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}.state{margin:auto;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ion-color-medium, #92949c);font-size:.9375rem;text-align:center;padding:24px}.state.empty ion-icon{font-size:2.5rem;opacity:.5}.date-sep{position:sticky;top:4px;z-index:1;display:flex;justify-content:center;margin:8px 0}.date-sep span{font-size:.75rem;color:var(--ion-color-medium, #92949c);background:var(--ion-color-step-100, rgba(127, 127, 127, .14));padding:3px 12px;border-radius:999px;backdrop-filter:blur(4px)}.scroll-down{position:absolute;right:14px;bottom:84px;width:40px;height:40px;border-radius:50%;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium, #92949c);box-shadow:0 2px 10px #00000026;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1.25rem;z-index:3}.scroll-down .dot{position:absolute;top:0;right:0;width:12px;height:12px;border-radius:50%;background:var(--ion-color-primary);border:2px solid var(--ion-card-background, #fff)}.closed-banner{text-align:center;padding:14px;color:var(--ion-color-medium, #92949c);font-size:.875rem;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}\n"] }]
|
|
73475
|
+
}], ctorParameters: () => [] });
|
|
72982
73476
|
|
|
72983
73477
|
const THREAD_PANEL_I18N = {
|
|
72984
73478
|
es: {
|
|
@@ -73136,7 +73630,7 @@ class ThreadPanelComponent {
|
|
|
73136
73630
|
</div>
|
|
73137
73631
|
}
|
|
73138
73632
|
</div>
|
|
73139
|
-
`, isInline: true, styles: [".thread-panel{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8)}.thread-header{padding:12px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff))}.thread-header .thread-title{font-size:1rem;font-weight:600;color:var(--ion-text-color, #000);margin:0}.thread-loading{display:flex;align-items:center;justify-content:center;flex:1;gap:10px;color:var(--ion-color-medium, #92949c);font-size:.875rem}.thread-chat{flex:1;overflow:hidden;display:flex;flex-direction:column}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ChatWindowComponent, selector: "val-chat-window", inputs: ["convId", "messages", "
|
|
73633
|
+
`, isInline: true, styles: [".thread-panel{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8)}.thread-header{padding:12px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff))}.thread-header .thread-title{font-size:1rem;font-weight:600;color:var(--ion-text-color, #000);margin:0}.thread-loading{display:flex;align-items:center;justify-content:center;flex:1;gap:10px;color:var(--ion-color-medium, #92949c);font-size:.875rem}.thread-chat{flex:1;overflow:hidden;display:flex;flex-direction:column}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ChatWindowComponent, selector: "val-chat-window", inputs: ["convId", "messages", "currentUserId", "isOpen", "typingUsers", "isLoading"], outputs: ["sendMessage", "loadMore", "reactionClick", "deleteMessage", "replyTo", "editMessage", "typing", "attach", "voice"] }] }); }
|
|
73140
73634
|
}
|
|
73141
73635
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThreadPanelComponent, decorators: [{
|
|
73142
73636
|
type: Component,
|
|
@@ -73190,5 +73684,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73190
73684
|
* Generated bundle index. Do not edit.
|
|
73191
73685
|
*/
|
|
73192
73686
|
|
|
73193
|
-
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, ConversationService, CookieBannerComponent, CookieSettingsComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_BUTTON_PRESETS, 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_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_POST_UPDATE_GRACE_MS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HapticsService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HrefComponent, HtmlViewerModalComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OptionCardsComponent, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, 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, SectionHeaderComponent, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, ThreadPanelComponent, TicketCardComponent, TicketCardImageService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, TypingIndicatorComponent, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_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_REACTIONS_CONFIG, VALTECH_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VERSION, ValtechErrorService, VerifyViewComponent, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, canSubmitRequestType, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, rbacGuard, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, selectableRequestTypes, storagePaths, superAdminGuard, toArticle };
|
|
73687
|
+
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatComposerComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, ConversationListItemComponent, ConversationService, CookieBannerComponent, CookieSettingsComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_BUTTON_PRESETS, 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_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_POST_UPDATE_GRACE_MS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HapticsService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HrefComponent, HtmlViewerModalComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OptionCardsComponent, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, 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, SectionHeaderComponent, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, ThreadPanelComponent, TicketCardComponent, TicketCardImageService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, TypingIndicatorComponent, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_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_REACTIONS_CONFIG, VALTECH_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VERSION, ValtechErrorService, VerifyViewComponent, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, canSubmitRequestType, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, formatClockTime, formatDateSeparator, formatRelativeTime, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, rbacGuard, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, selectableRequestTypes, storagePaths, superAdminGuard, toArticle };
|
|
73194
73688
|
//# sourceMappingURL=valtech-components.mjs.map
|