valtech-components 4.0.228 → 4.0.230
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 +431 -0
- package/esm2022/lib/components/molecules/chat-input/chat-input.component.mjs +1 -9
- 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 +274 -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 +213 -203
- package/esm2022/lib/components/organisms/thread-panel/thread-panel.component.mjs +13 -11
- package/esm2022/lib/services/chat/conversation.service.mjs +37 -14
- 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 +1120 -429
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/chat-composer/chat-composer.component.d.ts +75 -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 +34 -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 -56
- package/lib/components/organisms/thread-panel/thread-panel.component.d.ts +4 -1
- package/lib/services/chat/conversation.service.d.ts +12 -5
- package/lib/services/chat/types.d.ts +86 -52
- 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
- package/src/lib/services/firebase/firebase-messaging-sw.js +70 -125
|
@@ -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.230';
|
|
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',
|
|
@@ -72040,21 +72097,29 @@ class ConversationService {
|
|
|
72040
72097
|
}
|
|
72041
72098
|
/**
|
|
72042
72099
|
* Crea una nueva conversacion.
|
|
72100
|
+
* El backend responde con un envelope { operationId, conversation } — lo
|
|
72101
|
+
* desenvolvemos para devolver la Conversation directa.
|
|
72043
72102
|
*/
|
|
72044
72103
|
createConversation(req) {
|
|
72045
|
-
return this.http
|
|
72104
|
+
return this.http
|
|
72105
|
+
.post(`${this.baseUrl}/conversations`, req)
|
|
72106
|
+
.pipe(map(r => r.conversation));
|
|
72046
72107
|
}
|
|
72047
72108
|
/**
|
|
72048
|
-
* Obtiene o crea una conversacion asociada a una entidad (post, request, etc.).
|
|
72109
|
+
* Obtiene o crea una conversacion asociada a una entidad (post, request, org, etc.).
|
|
72049
72110
|
* Usa el patron upsert del backend: si ya existe una conversacion para esa entidad,
|
|
72050
72111
|
* la devuelve; si no, la crea.
|
|
72112
|
+
*
|
|
72113
|
+
* El backend espera { entityRef: { type, id }, type } y responde con un envelope
|
|
72114
|
+
* { operationId, conversation, created }.
|
|
72051
72115
|
*/
|
|
72052
|
-
getOrCreateForEntity(entityType, entityId, type = '
|
|
72053
|
-
return this.http
|
|
72054
|
-
|
|
72055
|
-
entityId,
|
|
72116
|
+
getOrCreateForEntity(entityType, entityId, type = 'thread') {
|
|
72117
|
+
return this.http
|
|
72118
|
+
.post(`${this.baseUrl}/entity-conversations`, {
|
|
72119
|
+
entityRef: { type: entityType, id: entityId },
|
|
72056
72120
|
type,
|
|
72057
|
-
})
|
|
72121
|
+
})
|
|
72122
|
+
.pipe(map(r => r.conversation));
|
|
72058
72123
|
}
|
|
72059
72124
|
/**
|
|
72060
72125
|
* Lista las conversaciones del usuario autenticado.
|
|
@@ -72069,13 +72134,17 @@ class ConversationService {
|
|
|
72069
72134
|
* Obtiene una conversacion por ID.
|
|
72070
72135
|
*/
|
|
72071
72136
|
getConversation(convId) {
|
|
72072
|
-
return this.http
|
|
72137
|
+
return this.http
|
|
72138
|
+
.get(`${this.baseUrl}/conversations/${convId}`)
|
|
72139
|
+
.pipe(map(r => r.conversation));
|
|
72073
72140
|
}
|
|
72074
72141
|
/**
|
|
72075
|
-
* Actualiza el
|
|
72142
|
+
* Actualiza el nombre o settings de una conversacion.
|
|
72076
72143
|
*/
|
|
72077
72144
|
updateConversation(convId, req) {
|
|
72078
|
-
return this.http
|
|
72145
|
+
return this.http
|
|
72146
|
+
.patch(`${this.baseUrl}/conversations/${convId}`, req)
|
|
72147
|
+
.pipe(map(r => r.conversation));
|
|
72079
72148
|
}
|
|
72080
72149
|
/**
|
|
72081
72150
|
* Abre una conversacion cerrada.
|
|
@@ -72102,10 +72171,21 @@ class ConversationService {
|
|
|
72102
72171
|
return this.http.delete(`${this.baseUrl}/conversations/${convId}/participants/${userId}`);
|
|
72103
72172
|
}
|
|
72104
72173
|
/**
|
|
72105
|
-
* Envia un mensaje a una conversacion.
|
|
72174
|
+
* Envia un mensaje a una conversacion, opcionalmente con adjuntos.
|
|
72175
|
+
* El backend espera `attachments: { url, type, name, size }[]` (key `type` = MIME),
|
|
72176
|
+
* mientras que el front usa `mimeType` — se mapea aqui.
|
|
72106
72177
|
*/
|
|
72107
|
-
sendMessage(convId, body) {
|
|
72108
|
-
|
|
72178
|
+
sendMessage(convId, body, attachments) {
|
|
72179
|
+
const payload = { body };
|
|
72180
|
+
if (attachments?.length) {
|
|
72181
|
+
payload['attachments'] = attachments.map(a => ({
|
|
72182
|
+
url: a.url,
|
|
72183
|
+
type: a.mimeType,
|
|
72184
|
+
name: a.name,
|
|
72185
|
+
size: a.size ?? 0,
|
|
72186
|
+
}));
|
|
72187
|
+
}
|
|
72188
|
+
return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, payload);
|
|
72109
72189
|
}
|
|
72110
72190
|
/**
|
|
72111
72191
|
* Elimina (marca como eliminado) un mensaje.
|
|
@@ -72274,241 +72354,323 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72274
72354
|
*/
|
|
72275
72355
|
|
|
72276
72356
|
addIcons({
|
|
72357
|
+
alertCircle,
|
|
72277
72358
|
checkmark,
|
|
72278
72359
|
checkmarkDone,
|
|
72360
|
+
createOutline,
|
|
72361
|
+
documentOutline,
|
|
72362
|
+
happyOutline,
|
|
72363
|
+
arrowUndoOutline,
|
|
72279
72364
|
timeOutline,
|
|
72280
|
-
|
|
72281
|
-
pencil,
|
|
72282
|
-
trash,
|
|
72283
|
-
returnDownBackOutline,
|
|
72365
|
+
trashOutline,
|
|
72284
72366
|
});
|
|
72285
|
-
const
|
|
72367
|
+
const MESSAGE_BUBBLE_I18N = {
|
|
72286
72368
|
es: {
|
|
72287
|
-
messageDeleted: '[mensaje eliminado]',
|
|
72288
|
-
edited: '(editado)',
|
|
72289
|
-
react: 'Reaccionar',
|
|
72290
72369
|
reply: 'Responder',
|
|
72291
72370
|
edit: 'Editar',
|
|
72292
72371
|
delete: 'Eliminar',
|
|
72372
|
+
react: 'Reaccionar',
|
|
72373
|
+
deleted: 'Mensaje eliminado',
|
|
72374
|
+
edited: 'editado',
|
|
72293
72375
|
},
|
|
72294
72376
|
en: {
|
|
72295
|
-
messageDeleted: '[message deleted]',
|
|
72296
|
-
edited: '(edited)',
|
|
72297
|
-
react: 'React',
|
|
72298
72377
|
reply: 'Reply',
|
|
72299
72378
|
edit: 'Edit',
|
|
72300
72379
|
delete: 'Delete',
|
|
72380
|
+
react: 'React',
|
|
72381
|
+
deleted: 'Message deleted',
|
|
72382
|
+
edited: 'edited',
|
|
72301
72383
|
},
|
|
72302
72384
|
};
|
|
72303
72385
|
/**
|
|
72304
|
-
* val-message-bubble
|
|
72386
|
+
* val-message-bubble — burbuja de mensaje presentacional (signal inputs).
|
|
72305
72387
|
*
|
|
72306
|
-
*
|
|
72307
|
-
*
|
|
72308
|
-
*
|
|
72388
|
+
* Sin lógica de negocio: el contenedor (`val-chat-window`) decide agrupación y
|
|
72389
|
+
* pasa flags (showAvatar/showName/tail). Las acciones (responder/editar/borrar/
|
|
72390
|
+
* reaccionar) NO están siempre visibles: se revelan en hover (desktop) o al tocar
|
|
72391
|
+
* la burbuja (móvil), y se emiten por un único `(action)` tipado.
|
|
72309
72392
|
*
|
|
72310
|
-
*
|
|
72311
|
-
*
|
|
72312
|
-
* [props]="msg"
|
|
72313
|
-
* (reactionClick)="onReaction($event)"
|
|
72314
|
-
* (replyClick)="onReply($event)"
|
|
72315
|
-
* (deleteClick)="onDelete($event)"
|
|
72316
|
-
* />
|
|
72393
|
+
* Read receipts estilo WhatsApp: sending → reloj, sent → ✓, delivered/read → ✓✓
|
|
72394
|
+
* (read en color), failed → alerta.
|
|
72317
72395
|
*/
|
|
72318
72396
|
class MessageBubbleComponent {
|
|
72319
72397
|
constructor() {
|
|
72320
72398
|
this.i18n = inject(I18nService);
|
|
72321
|
-
this.
|
|
72322
|
-
this.
|
|
72323
|
-
this.
|
|
72324
|
-
this.
|
|
72325
|
-
|
|
72326
|
-
|
|
72327
|
-
|
|
72328
|
-
|
|
72329
|
-
|
|
72330
|
-
|
|
72331
|
-
|
|
72332
|
-
|
|
72333
|
-
|
|
72334
|
-
|
|
72335
|
-
|
|
72336
|
-
|
|
72337
|
-
|
|
72338
|
-
|
|
72339
|
-
|
|
72340
|
-
|
|
72341
|
-
|
|
72342
|
-
|
|
72343
|
-
|
|
72344
|
-
|
|
72345
|
-
|
|
72346
|
-
|
|
72347
|
-
|
|
72348
|
-
|
|
72349
|
-
|
|
72350
|
-
|
|
72351
|
-
|
|
72352
|
-
|
|
72353
|
-
|
|
72354
|
-
}
|
|
72355
|
-
onReactionClick(reaction) {
|
|
72356
|
-
this.reactionClick.emit({ token: reaction.token, msgId: this.props.msgId });
|
|
72399
|
+
this.msg = input.required();
|
|
72400
|
+
this.showAvatar = input(false);
|
|
72401
|
+
this.showName = input(false);
|
|
72402
|
+
this.tail = input(true);
|
|
72403
|
+
this.locale = input('es-CL');
|
|
72404
|
+
this.action = output();
|
|
72405
|
+
this.actionsOpen = signal(false);
|
|
72406
|
+
this.time = computed(() => formatClockTime(this.msg().createdAt, this.locale()));
|
|
72407
|
+
this.initials = computed(() => {
|
|
72408
|
+
const name = this.msg().senderName?.trim() ?? '';
|
|
72409
|
+
if (!name)
|
|
72410
|
+
return '?';
|
|
72411
|
+
const parts = name.split(/\s+/);
|
|
72412
|
+
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
72413
|
+
});
|
|
72414
|
+
this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
|
|
72415
|
+
this.audioAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('audio/')));
|
|
72416
|
+
this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/') && !a.mimeType?.startsWith('audio/')));
|
|
72417
|
+
this.statusIcon = computed(() => {
|
|
72418
|
+
switch (this.msg().status) {
|
|
72419
|
+
case 'sending':
|
|
72420
|
+
return 'time-outline';
|
|
72421
|
+
case 'failed':
|
|
72422
|
+
return 'alert-circle';
|
|
72423
|
+
case 'delivered':
|
|
72424
|
+
case 'read':
|
|
72425
|
+
return 'checkmark-done';
|
|
72426
|
+
case 'sent':
|
|
72427
|
+
default:
|
|
72428
|
+
return 'checkmark';
|
|
72429
|
+
}
|
|
72430
|
+
});
|
|
72431
|
+
this.i18n.registerDefaults('MessageBubble', MESSAGE_BUBBLE_I18N);
|
|
72357
72432
|
}
|
|
72358
|
-
|
|
72359
|
-
this.
|
|
72433
|
+
toggleActions() {
|
|
72434
|
+
if (this.msg().isDeleted)
|
|
72435
|
+
return;
|
|
72436
|
+
this.actionsOpen.update(v => !v);
|
|
72360
72437
|
}
|
|
72361
|
-
|
|
72362
|
-
this.
|
|
72438
|
+
emit(type, token) {
|
|
72439
|
+
this.actionsOpen.set(false);
|
|
72440
|
+
this.action.emit({ type, msgId: this.msg().msgId, token });
|
|
72363
72441
|
}
|
|
72364
|
-
|
|
72365
|
-
this.
|
|
72442
|
+
t(key) {
|
|
72443
|
+
return this.i18n.t(key, 'MessageBubble');
|
|
72366
72444
|
}
|
|
72367
72445
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72368
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MessageBubbleComponent, isStandalone: true, selector: "val-message-bubble", inputs: {
|
|
72369
|
-
<div class="
|
|
72370
|
-
@if (
|
|
72371
|
-
<
|
|
72446
|
+
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: `
|
|
72447
|
+
<div class="row" [class.mine]="msg().isMine" [class.tail]="tail()" [class.with-avatar]="showAvatar()">
|
|
72448
|
+
@if (showAvatar() && !msg().isMine) {
|
|
72449
|
+
<div class="avatar" [class.hidden]="!tail()">
|
|
72450
|
+
@if (msg().senderAvatar) {
|
|
72451
|
+
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72452
|
+
} @else {
|
|
72453
|
+
<span>{{ initials() }}</span>
|
|
72454
|
+
}
|
|
72455
|
+
</div>
|
|
72372
72456
|
}
|
|
72373
72457
|
|
|
72374
|
-
<div class="bubble">
|
|
72375
|
-
|
|
72376
|
-
|
|
72377
|
-
|
|
72378
|
-
|
|
72379
|
-
|
|
72380
|
-
|
|
72381
|
-
|
|
72382
|
-
|
|
72383
|
-
|
|
72384
|
-
} @else {
|
|
72385
|
-
<div class="bubble-body">{{ props.body }}</div>
|
|
72386
|
-
}
|
|
72387
|
-
|
|
72388
|
-
<div class="bubble-meta">
|
|
72389
|
-
<span class="timestamp">{{ formatTime(props.createdAt) }}</span>
|
|
72390
|
-
@if (props.isEdited && !props.isDeleted) {
|
|
72391
|
-
<span class="edited-label">{{ editedLabel() }}</span>
|
|
72392
|
-
}
|
|
72393
|
-
@if (props.isMine && props.status) {
|
|
72394
|
-
<ion-icon
|
|
72395
|
-
[name]="statusIcon()"
|
|
72396
|
-
[class]="'status-icon ' + (props.status ?? '')"
|
|
72397
|
-
aria-hidden="true"
|
|
72398
|
-
></ion-icon>
|
|
72458
|
+
<div class="bubble-wrap">
|
|
72459
|
+
<div
|
|
72460
|
+
class="bubble"
|
|
72461
|
+
[class.deleted]="msg().isDeleted"
|
|
72462
|
+
(click)="toggleActions()"
|
|
72463
|
+
(keyup.enter)="toggleActions()"
|
|
72464
|
+
tabindex="0"
|
|
72465
|
+
>
|
|
72466
|
+
@if (showName() && !msg().isMine && !msg().isDeleted) {
|
|
72467
|
+
<span class="name">{{ msg().senderName }}</span>
|
|
72399
72468
|
}
|
|
72400
|
-
</div>
|
|
72401
|
-
</div>
|
|
72402
72469
|
|
|
72403
|
-
|
|
72404
|
-
|
|
72405
|
-
|
|
72406
|
-
|
|
72407
|
-
|
|
72408
|
-
@if (reaction.count > 0) {
|
|
72409
|
-
<span class="reaction-count">{{ reaction.count }}</span>
|
|
72410
|
-
}
|
|
72411
|
-
</button>
|
|
72470
|
+
@if (msg().replyTo && !msg().isDeleted) {
|
|
72471
|
+
<div class="reply-preview">
|
|
72472
|
+
<span class="reply-name">{{ msg().replyTo!.senderName }}</span>
|
|
72473
|
+
<span class="reply-body">{{ msg().replyTo!.body }}</span>
|
|
72474
|
+
</div>
|
|
72412
72475
|
}
|
|
72413
|
-
</div>
|
|
72414
|
-
}
|
|
72415
72476
|
|
|
72416
|
-
|
|
72417
|
-
|
|
72418
|
-
|
|
72419
|
-
|
|
72420
|
-
|
|
72421
|
-
|
|
72422
|
-
|
|
72423
|
-
|
|
72424
|
-
|
|
72425
|
-
|
|
72426
|
-
|
|
72427
|
-
|
|
72477
|
+
@if (msg().isDeleted) {
|
|
72478
|
+
<span class="body deleted-text">{{ t('deleted') }}</span>
|
|
72479
|
+
} @else {
|
|
72480
|
+
@if (imageAttachments().length > 0) {
|
|
72481
|
+
<div class="attachments">
|
|
72482
|
+
@for (att of imageAttachments(); track att.url) {
|
|
72483
|
+
<img class="att-image" [src]="att.url" [alt]="att.name" />
|
|
72484
|
+
}
|
|
72485
|
+
</div>
|
|
72486
|
+
}
|
|
72487
|
+
@if (audioAttachments().length > 0) {
|
|
72488
|
+
<div class="audios">
|
|
72489
|
+
@for (att of audioAttachments(); track att.url) {
|
|
72490
|
+
<audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
|
|
72491
|
+
}
|
|
72492
|
+
</div>
|
|
72493
|
+
}
|
|
72494
|
+
@if (fileAttachments().length > 0) {
|
|
72495
|
+
<div class="files">
|
|
72496
|
+
@for (att of fileAttachments(); track att.url) {
|
|
72497
|
+
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72498
|
+
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72499
|
+
<span>{{ att.name }}</span>
|
|
72500
|
+
</a>
|
|
72501
|
+
}
|
|
72502
|
+
</div>
|
|
72503
|
+
}
|
|
72504
|
+
@if (msg().body) {
|
|
72505
|
+
<span class="body">{{ msg().body }}</span>
|
|
72506
|
+
}
|
|
72428
72507
|
}
|
|
72508
|
+
|
|
72509
|
+
<span class="meta">
|
|
72510
|
+
@if (msg().isEdited && !msg().isDeleted) {
|
|
72511
|
+
<span class="edited">{{ t('edited') }}</span>
|
|
72512
|
+
}
|
|
72513
|
+
<span class="time">{{ time() }}</span>
|
|
72514
|
+
@if (msg().isMine && !msg().isDeleted) {
|
|
72515
|
+
<ion-icon
|
|
72516
|
+
class="status"
|
|
72517
|
+
[class.read]="msg().status === 'read'"
|
|
72518
|
+
[class.failed]="msg().status === 'failed'"
|
|
72519
|
+
[name]="statusIcon()"
|
|
72520
|
+
aria-hidden="true"
|
|
72521
|
+
/>
|
|
72522
|
+
}
|
|
72523
|
+
</span>
|
|
72429
72524
|
</div>
|
|
72430
|
-
}
|
|
72431
|
-
</div>
|
|
72432
|
-
`, 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"] }] }); }
|
|
72433
|
-
}
|
|
72434
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72435
|
-
type: Component,
|
|
72436
|
-
args: [{ selector: 'val-message-bubble', standalone: true, imports: [CommonModule, IonIcon, IonButton], template: `
|
|
72437
|
-
<div class="message-bubble-wrapper" [class.is-mine]="props.isMine">
|
|
72438
|
-
@if (!props.isMine && !props.isDeleted) {
|
|
72439
|
-
<span class="sender-name">{{ props.senderName }}</span>
|
|
72440
|
-
}
|
|
72441
72525
|
|
|
72442
|
-
|
|
72443
|
-
|
|
72444
|
-
|
|
72445
|
-
|
|
72446
|
-
|
|
72526
|
+
@if (msg().reactions && msg().reactions!.length > 0) {
|
|
72527
|
+
<div class="reactions">
|
|
72528
|
+
@for (r of msg().reactions!; track r.token) {
|
|
72529
|
+
<button class="reaction" [class.active]="r.active" (click)="emit('react', r.token)">
|
|
72530
|
+
<span>{{ r.token }}</span>
|
|
72531
|
+
<span class="count">{{ r.count }}</span>
|
|
72532
|
+
</button>
|
|
72533
|
+
}
|
|
72447
72534
|
</div>
|
|
72448
72535
|
}
|
|
72449
72536
|
|
|
72450
|
-
@if (
|
|
72451
|
-
<
|
|
72452
|
-
|
|
72453
|
-
|
|
72537
|
+
@if (!msg().isDeleted) {
|
|
72538
|
+
<div class="actions" [class.open]="actionsOpen()">
|
|
72539
|
+
<button class="act" [attr.aria-label]="t('react')" (click)="emit('react', '👍')">
|
|
72540
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72541
|
+
</button>
|
|
72542
|
+
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72543
|
+
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72544
|
+
</button>
|
|
72545
|
+
@if (msg().isMine) {
|
|
72546
|
+
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72547
|
+
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72548
|
+
</button>
|
|
72549
|
+
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72550
|
+
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72551
|
+
</button>
|
|
72552
|
+
}
|
|
72553
|
+
</div>
|
|
72454
72554
|
}
|
|
72455
|
-
|
|
72456
|
-
<div class="bubble-meta">
|
|
72457
|
-
<span class="timestamp">{{ formatTime(props.createdAt) }}</span>
|
|
72458
|
-
@if (props.isEdited && !props.isDeleted) {
|
|
72459
|
-
<span class="edited-label">{{ editedLabel() }}</span>
|
|
72460
|
-
}
|
|
72461
|
-
@if (props.isMine && props.status) {
|
|
72462
|
-
<ion-icon
|
|
72463
|
-
[name]="statusIcon()"
|
|
72464
|
-
[class]="'status-icon ' + (props.status ?? '')"
|
|
72465
|
-
aria-hidden="true"
|
|
72466
|
-
></ion-icon>
|
|
72467
|
-
}
|
|
72468
|
-
</div>
|
|
72469
72555
|
</div>
|
|
72470
|
-
|
|
72471
|
-
|
|
72472
|
-
|
|
72473
|
-
|
|
72474
|
-
|
|
72475
|
-
|
|
72476
|
-
|
|
72477
|
-
|
|
72478
|
-
|
|
72479
|
-
|
|
72556
|
+
</div>
|
|
72557
|
+
`, 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}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.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"] }] }); }
|
|
72558
|
+
}
|
|
72559
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72560
|
+
type: Component,
|
|
72561
|
+
args: [{ selector: 'val-message-bubble', standalone: true, imports: [CommonModule, IonIcon], template: `
|
|
72562
|
+
<div class="row" [class.mine]="msg().isMine" [class.tail]="tail()" [class.with-avatar]="showAvatar()">
|
|
72563
|
+
@if (showAvatar() && !msg().isMine) {
|
|
72564
|
+
<div class="avatar" [class.hidden]="!tail()">
|
|
72565
|
+
@if (msg().senderAvatar) {
|
|
72566
|
+
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72567
|
+
} @else {
|
|
72568
|
+
<span>{{ initials() }}</span>
|
|
72480
72569
|
}
|
|
72481
72570
|
</div>
|
|
72482
72571
|
}
|
|
72483
72572
|
|
|
72484
|
-
|
|
72485
|
-
<div
|
|
72486
|
-
|
|
72487
|
-
|
|
72488
|
-
|
|
72489
|
-
|
|
72490
|
-
|
|
72491
|
-
|
|
72492
|
-
|
|
72493
|
-
<
|
|
72494
|
-
|
|
72495
|
-
|
|
72573
|
+
<div class="bubble-wrap">
|
|
72574
|
+
<div
|
|
72575
|
+
class="bubble"
|
|
72576
|
+
[class.deleted]="msg().isDeleted"
|
|
72577
|
+
(click)="toggleActions()"
|
|
72578
|
+
(keyup.enter)="toggleActions()"
|
|
72579
|
+
tabindex="0"
|
|
72580
|
+
>
|
|
72581
|
+
@if (showName() && !msg().isMine && !msg().isDeleted) {
|
|
72582
|
+
<span class="name">{{ msg().senderName }}</span>
|
|
72583
|
+
}
|
|
72584
|
+
|
|
72585
|
+
@if (msg().replyTo && !msg().isDeleted) {
|
|
72586
|
+
<div class="reply-preview">
|
|
72587
|
+
<span class="reply-name">{{ msg().replyTo!.senderName }}</span>
|
|
72588
|
+
<span class="reply-body">{{ msg().replyTo!.body }}</span>
|
|
72589
|
+
</div>
|
|
72590
|
+
}
|
|
72591
|
+
|
|
72592
|
+
@if (msg().isDeleted) {
|
|
72593
|
+
<span class="body deleted-text">{{ t('deleted') }}</span>
|
|
72594
|
+
} @else {
|
|
72595
|
+
@if (imageAttachments().length > 0) {
|
|
72596
|
+
<div class="attachments">
|
|
72597
|
+
@for (att of imageAttachments(); track att.url) {
|
|
72598
|
+
<img class="att-image" [src]="att.url" [alt]="att.name" />
|
|
72599
|
+
}
|
|
72600
|
+
</div>
|
|
72601
|
+
}
|
|
72602
|
+
@if (audioAttachments().length > 0) {
|
|
72603
|
+
<div class="audios">
|
|
72604
|
+
@for (att of audioAttachments(); track att.url) {
|
|
72605
|
+
<audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
|
|
72606
|
+
}
|
|
72607
|
+
</div>
|
|
72608
|
+
}
|
|
72609
|
+
@if (fileAttachments().length > 0) {
|
|
72610
|
+
<div class="files">
|
|
72611
|
+
@for (att of fileAttachments(); track att.url) {
|
|
72612
|
+
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72613
|
+
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72614
|
+
<span>{{ att.name }}</span>
|
|
72615
|
+
</a>
|
|
72616
|
+
}
|
|
72617
|
+
</div>
|
|
72618
|
+
}
|
|
72619
|
+
@if (msg().body) {
|
|
72620
|
+
<span class="body">{{ msg().body }}</span>
|
|
72621
|
+
}
|
|
72496
72622
|
}
|
|
72623
|
+
|
|
72624
|
+
<span class="meta">
|
|
72625
|
+
@if (msg().isEdited && !msg().isDeleted) {
|
|
72626
|
+
<span class="edited">{{ t('edited') }}</span>
|
|
72627
|
+
}
|
|
72628
|
+
<span class="time">{{ time() }}</span>
|
|
72629
|
+
@if (msg().isMine && !msg().isDeleted) {
|
|
72630
|
+
<ion-icon
|
|
72631
|
+
class="status"
|
|
72632
|
+
[class.read]="msg().status === 'read'"
|
|
72633
|
+
[class.failed]="msg().status === 'failed'"
|
|
72634
|
+
[name]="statusIcon()"
|
|
72635
|
+
aria-hidden="true"
|
|
72636
|
+
/>
|
|
72637
|
+
}
|
|
72638
|
+
</span>
|
|
72497
72639
|
</div>
|
|
72498
|
-
|
|
72640
|
+
|
|
72641
|
+
@if (msg().reactions && msg().reactions!.length > 0) {
|
|
72642
|
+
<div class="reactions">
|
|
72643
|
+
@for (r of msg().reactions!; track r.token) {
|
|
72644
|
+
<button class="reaction" [class.active]="r.active" (click)="emit('react', r.token)">
|
|
72645
|
+
<span>{{ r.token }}</span>
|
|
72646
|
+
<span class="count">{{ r.count }}</span>
|
|
72647
|
+
</button>
|
|
72648
|
+
}
|
|
72649
|
+
</div>
|
|
72650
|
+
}
|
|
72651
|
+
|
|
72652
|
+
@if (!msg().isDeleted) {
|
|
72653
|
+
<div class="actions" [class.open]="actionsOpen()">
|
|
72654
|
+
<button class="act" [attr.aria-label]="t('react')" (click)="emit('react', '👍')">
|
|
72655
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72656
|
+
</button>
|
|
72657
|
+
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72658
|
+
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72659
|
+
</button>
|
|
72660
|
+
@if (msg().isMine) {
|
|
72661
|
+
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72662
|
+
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72663
|
+
</button>
|
|
72664
|
+
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72665
|
+
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72666
|
+
</button>
|
|
72667
|
+
}
|
|
72668
|
+
</div>
|
|
72669
|
+
}
|
|
72670
|
+
</div>
|
|
72499
72671
|
</div>
|
|
72500
|
-
`, styles: [".
|
|
72501
|
-
}], ctorParameters: () => []
|
|
72502
|
-
type: Input
|
|
72503
|
-
}], reactionClick: [{
|
|
72504
|
-
type: Output
|
|
72505
|
-
}], replyClick: [{
|
|
72506
|
-
type: Output
|
|
72507
|
-
}], deleteClick: [{
|
|
72508
|
-
type: Output
|
|
72509
|
-
}], editClick: [{
|
|
72510
|
-
type: Output
|
|
72511
|
-
}] } });
|
|
72672
|
+
`, 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}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.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"] }]
|
|
72673
|
+
}], ctorParameters: () => [] });
|
|
72512
72674
|
|
|
72513
72675
|
addIcons({ sendOutline });
|
|
72514
72676
|
const CHAT_INPUT_I18N = {
|
|
@@ -72601,10 +72763,6 @@ class ChatInputComponent {
|
|
|
72601
72763
|
[autoGrow]="true"
|
|
72602
72764
|
[rows]="1"
|
|
72603
72765
|
[value]="body()"
|
|
72604
|
-
enterkeyhint="send"
|
|
72605
|
-
autocomplete="off"
|
|
72606
|
-
autocorrect="off"
|
|
72607
|
-
spellcheck="false"
|
|
72608
72766
|
(ionInput)="onInput($event)"
|
|
72609
72767
|
(keydown)="onKeydown($event)"
|
|
72610
72768
|
></ion-textarea>
|
|
@@ -72637,10 +72795,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72637
72795
|
[autoGrow]="true"
|
|
72638
72796
|
[rows]="1"
|
|
72639
72797
|
[value]="body()"
|
|
72640
|
-
enterkeyhint="send"
|
|
72641
|
-
autocomplete="off"
|
|
72642
|
-
autocorrect="off"
|
|
72643
|
-
spellcheck="false"
|
|
72644
72798
|
(ionInput)="onInput($event)"
|
|
72645
72799
|
(keydown)="onKeydown($event)"
|
|
72646
72800
|
></ion-textarea>
|
|
@@ -72668,6 +72822,533 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72668
72822
|
type: Output
|
|
72669
72823
|
}] } });
|
|
72670
72824
|
|
|
72825
|
+
addIcons({ addOutline, checkmarkOutline, closeOutline, documentOutline, micOutline, arrowUpOutline, trashOutline });
|
|
72826
|
+
const CHAT_COMPOSER_I18N = {
|
|
72827
|
+
es: {
|
|
72828
|
+
placeholder: 'Escribe un mensaje',
|
|
72829
|
+
send: 'Enviar',
|
|
72830
|
+
attach: 'Adjuntar',
|
|
72831
|
+
voice: 'Mensaje de voz',
|
|
72832
|
+
done: 'Listo',
|
|
72833
|
+
replyingTo: 'Respondiendo a',
|
|
72834
|
+
cancel: 'Cancelar',
|
|
72835
|
+
remove: 'Quitar',
|
|
72836
|
+
},
|
|
72837
|
+
en: {
|
|
72838
|
+
placeholder: 'Write a message',
|
|
72839
|
+
send: 'Send',
|
|
72840
|
+
attach: 'Attach',
|
|
72841
|
+
voice: 'Voice message',
|
|
72842
|
+
done: 'Done',
|
|
72843
|
+
replyingTo: 'Replying to',
|
|
72844
|
+
cancel: 'Cancel',
|
|
72845
|
+
remove: 'Remove',
|
|
72846
|
+
},
|
|
72847
|
+
};
|
|
72848
|
+
/**
|
|
72849
|
+
* val-chat-composer — barra de redacción de chat moderna (signal inputs).
|
|
72850
|
+
*
|
|
72851
|
+
* Pill con: adjuntar (+), textarea auto-grow, y mic/enviar (mic cuando está vacío,
|
|
72852
|
+
* enviar cuando hay texto o adjuntos). Los adjuntos (botón + / pegar imagen) se
|
|
72853
|
+
* acumulan como PREVIEW (miniaturas) y se emiten por `(attach)` recién al ENVIAR
|
|
72854
|
+
* — no se van solos al chat. Audio grabado se emite directo al parar.
|
|
72855
|
+
* No incluye picker de emoji: el teclado del sistema ya lo trae.
|
|
72856
|
+
*/
|
|
72857
|
+
class ChatComposerComponent {
|
|
72858
|
+
constructor() {
|
|
72859
|
+
this.i18n = inject(I18nService);
|
|
72860
|
+
this.placeholder = input('');
|
|
72861
|
+
this.disabled = input(false);
|
|
72862
|
+
this.maxLength = input(2000);
|
|
72863
|
+
this.replyingTo = input(null);
|
|
72864
|
+
this.showAttach = input(true);
|
|
72865
|
+
this.showMic = input(true);
|
|
72866
|
+
this.send = output();
|
|
72867
|
+
this.typing = output();
|
|
72868
|
+
this.voice = output();
|
|
72869
|
+
this.cancelReply = output();
|
|
72870
|
+
this.body = signal('');
|
|
72871
|
+
this.pending = signal([]);
|
|
72872
|
+
this.pendingId = 0;
|
|
72873
|
+
// Grabación de audio (MediaRecorder).
|
|
72874
|
+
this.recording = signal(false);
|
|
72875
|
+
this.recSeconds = signal(0);
|
|
72876
|
+
this.recChunks = [];
|
|
72877
|
+
this.recTimeLabel = computed(() => {
|
|
72878
|
+
const s = this.recSeconds();
|
|
72879
|
+
const mm = String(Math.floor(s / 60)).padStart(2, '0');
|
|
72880
|
+
const ss = String(s % 60).padStart(2, '0');
|
|
72881
|
+
return `${mm}:${ss}`;
|
|
72882
|
+
});
|
|
72883
|
+
this.lastTypingEmit = 0;
|
|
72884
|
+
this.canSend = computed(() => {
|
|
72885
|
+
const t = this.body().trim();
|
|
72886
|
+
if (this.disabled())
|
|
72887
|
+
return false;
|
|
72888
|
+
return (t.length > 0 && t.length <= this.maxLength()) || this.pending().length > 0;
|
|
72889
|
+
});
|
|
72890
|
+
this.placeholderText = computed(() => {
|
|
72891
|
+
this.i18n.lang();
|
|
72892
|
+
return this.placeholder() || this.i18n.t('placeholder', 'ChatComposer');
|
|
72893
|
+
});
|
|
72894
|
+
this.i18n.registerDefaults('ChatComposer', CHAT_COMPOSER_I18N);
|
|
72895
|
+
}
|
|
72896
|
+
onInput(event) {
|
|
72897
|
+
const target = event.target;
|
|
72898
|
+
this.body.set(target.value ?? '');
|
|
72899
|
+
this.emitTypingDebounced();
|
|
72900
|
+
}
|
|
72901
|
+
onKeydown(event) {
|
|
72902
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
72903
|
+
event.preventDefault();
|
|
72904
|
+
this.onSend();
|
|
72905
|
+
}
|
|
72906
|
+
}
|
|
72907
|
+
onSend() {
|
|
72908
|
+
if (!this.canSend())
|
|
72909
|
+
return;
|
|
72910
|
+
// Un solo evento: texto (caption) + todos los adjuntos staged. El consumer
|
|
72911
|
+
// los sube y manda como UN mensaje con body + attachments.
|
|
72912
|
+
const files = this.pending().map(p => p.file);
|
|
72913
|
+
const text = this.body().trim();
|
|
72914
|
+
this.send.emit({ text, files });
|
|
72915
|
+
for (const att of this.pending()) {
|
|
72916
|
+
if (att.url)
|
|
72917
|
+
URL.revokeObjectURL(att.url);
|
|
72918
|
+
}
|
|
72919
|
+
this.pending.set([]);
|
|
72920
|
+
this.body.set('');
|
|
72921
|
+
}
|
|
72922
|
+
onFile(event) {
|
|
72923
|
+
const input = event.target;
|
|
72924
|
+
const file = input.files?.[0];
|
|
72925
|
+
if (file)
|
|
72926
|
+
this.addPending(file);
|
|
72927
|
+
input.value = '';
|
|
72928
|
+
}
|
|
72929
|
+
/** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
|
|
72930
|
+
onPaste(event) {
|
|
72931
|
+
const items = event.clipboardData?.items;
|
|
72932
|
+
if (!items)
|
|
72933
|
+
return;
|
|
72934
|
+
for (const item of Array.from(items)) {
|
|
72935
|
+
if (item.type.startsWith('image/')) {
|
|
72936
|
+
const file = item.getAsFile();
|
|
72937
|
+
if (file) {
|
|
72938
|
+
event.preventDefault();
|
|
72939
|
+
this.addPending(file);
|
|
72940
|
+
}
|
|
72941
|
+
return;
|
|
72942
|
+
}
|
|
72943
|
+
}
|
|
72944
|
+
}
|
|
72945
|
+
addPending(file) {
|
|
72946
|
+
const kind = file.type.startsWith('image/')
|
|
72947
|
+
? 'image'
|
|
72948
|
+
: file.type.startsWith('audio/')
|
|
72949
|
+
? 'audio'
|
|
72950
|
+
: 'file';
|
|
72951
|
+
const url = kind === 'file' ? undefined : URL.createObjectURL(file);
|
|
72952
|
+
this.pending.update(arr => [...arr, { id: this.pendingId++, file, url, kind }]);
|
|
72953
|
+
}
|
|
72954
|
+
removePending(id) {
|
|
72955
|
+
this.pending.update(arr => {
|
|
72956
|
+
const found = arr.find(a => a.id === id);
|
|
72957
|
+
if (found?.url)
|
|
72958
|
+
URL.revokeObjectURL(found.url);
|
|
72959
|
+
return arr.filter(a => a.id !== id);
|
|
72960
|
+
});
|
|
72961
|
+
}
|
|
72962
|
+
/**
|
|
72963
|
+
* Emite typing como máximo 1 vez cada 2s. performance.now es seguro en runtime.
|
|
72964
|
+
*/
|
|
72965
|
+
emitTypingDebounced() {
|
|
72966
|
+
const now = performance.now();
|
|
72967
|
+
if (now - this.lastTypingEmit > 2000) {
|
|
72968
|
+
this.lastTypingEmit = now;
|
|
72969
|
+
this.typing.emit();
|
|
72970
|
+
}
|
|
72971
|
+
}
|
|
72972
|
+
// ─── Grabación de audio ───────────────────────────────────────────────────
|
|
72973
|
+
/** Selecciona un mimeType de audio soportado por el navegador. */
|
|
72974
|
+
pickAudioMime() {
|
|
72975
|
+
const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
|
|
72976
|
+
const MR = window.MediaRecorder;
|
|
72977
|
+
if (!MR?.isTypeSupported)
|
|
72978
|
+
return '';
|
|
72979
|
+
return candidates.find(c => MR.isTypeSupported(c)) ?? '';
|
|
72980
|
+
}
|
|
72981
|
+
async startRecording() {
|
|
72982
|
+
if (this.recording())
|
|
72983
|
+
return;
|
|
72984
|
+
if (!navigator.mediaDevices?.getUserMedia || !('MediaRecorder' in window)) {
|
|
72985
|
+
this.voice.emit(); // sin soporte: deja que el consumer decida (fallback)
|
|
72986
|
+
return;
|
|
72987
|
+
}
|
|
72988
|
+
try {
|
|
72989
|
+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
72990
|
+
this.recStream = stream;
|
|
72991
|
+
const mimeType = this.pickAudioMime();
|
|
72992
|
+
this.recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
|
|
72993
|
+
this.recChunks = [];
|
|
72994
|
+
this.recorder.ondataavailable = e => {
|
|
72995
|
+
if (e.data.size > 0)
|
|
72996
|
+
this.recChunks.push(e.data);
|
|
72997
|
+
};
|
|
72998
|
+
this.recorder.start();
|
|
72999
|
+
this.recording.set(true);
|
|
73000
|
+
this.recSeconds.set(0);
|
|
73001
|
+
this.recTimer = setInterval(() => this.recSeconds.update(s => s + 1), 1000);
|
|
73002
|
+
}
|
|
73003
|
+
catch {
|
|
73004
|
+
this.cleanupRecording();
|
|
73005
|
+
}
|
|
73006
|
+
}
|
|
73007
|
+
/** Para la grabación y la stagea como adjunto pendiente (con preview de audio). */
|
|
73008
|
+
finishRecording() {
|
|
73009
|
+
const rec = this.recorder;
|
|
73010
|
+
if (!rec) {
|
|
73011
|
+
this.cleanupRecording();
|
|
73012
|
+
return;
|
|
73013
|
+
}
|
|
73014
|
+
rec.onstop = () => {
|
|
73015
|
+
const type = rec.mimeType || 'audio/webm';
|
|
73016
|
+
const blob = new Blob(this.recChunks, { type });
|
|
73017
|
+
const ext = type.includes('mp4') ? 'm4a' : 'webm';
|
|
73018
|
+
const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
|
|
73019
|
+
if (blob.size > 0)
|
|
73020
|
+
this.addPending(file);
|
|
73021
|
+
this.cleanupRecording();
|
|
73022
|
+
};
|
|
73023
|
+
rec.stop();
|
|
73024
|
+
}
|
|
73025
|
+
cancelRecording() {
|
|
73026
|
+
const rec = this.recorder;
|
|
73027
|
+
if (rec)
|
|
73028
|
+
rec.onstop = () => this.cleanupRecording();
|
|
73029
|
+
try {
|
|
73030
|
+
rec?.stop();
|
|
73031
|
+
}
|
|
73032
|
+
catch {
|
|
73033
|
+
this.cleanupRecording();
|
|
73034
|
+
}
|
|
73035
|
+
if (!rec)
|
|
73036
|
+
this.cleanupRecording();
|
|
73037
|
+
}
|
|
73038
|
+
cleanupRecording() {
|
|
73039
|
+
if (this.recTimer)
|
|
73040
|
+
clearInterval(this.recTimer);
|
|
73041
|
+
this.recTimer = undefined;
|
|
73042
|
+
this.recStream?.getTracks().forEach(t => t.stop());
|
|
73043
|
+
this.recStream = undefined;
|
|
73044
|
+
this.recorder = undefined;
|
|
73045
|
+
this.recChunks = [];
|
|
73046
|
+
this.recording.set(false);
|
|
73047
|
+
this.recSeconds.set(0);
|
|
73048
|
+
}
|
|
73049
|
+
ngOnDestroy() {
|
|
73050
|
+
this.cleanupRecording();
|
|
73051
|
+
for (const att of this.pending()) {
|
|
73052
|
+
if (att.url)
|
|
73053
|
+
URL.revokeObjectURL(att.url);
|
|
73054
|
+
}
|
|
73055
|
+
}
|
|
73056
|
+
t(key) {
|
|
73057
|
+
return this.i18n.t(key, 'ChatComposer');
|
|
73058
|
+
}
|
|
73059
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73060
|
+
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 }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
|
|
73061
|
+
<div class="composer">
|
|
73062
|
+
@if (replyingTo()) {
|
|
73063
|
+
<div class="reply-bar">
|
|
73064
|
+
<div class="reply-content">
|
|
73065
|
+
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
73066
|
+
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
73067
|
+
</div>
|
|
73068
|
+
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
73069
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73070
|
+
</button>
|
|
73071
|
+
</div>
|
|
73072
|
+
}
|
|
73073
|
+
|
|
73074
|
+
@if (pending().length > 0) {
|
|
73075
|
+
<div class="pending-row">
|
|
73076
|
+
@for (att of pending(); track att.id) {
|
|
73077
|
+
@if (att.kind === 'audio') {
|
|
73078
|
+
<div class="audio-chip">
|
|
73079
|
+
<audio controls preload="metadata" [src]="att.url"></audio>
|
|
73080
|
+
<button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
|
|
73081
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73082
|
+
</button>
|
|
73083
|
+
</div>
|
|
73084
|
+
} @else {
|
|
73085
|
+
<div class="thumb" [class.file]="att.kind === 'file'">
|
|
73086
|
+
@if (att.kind === 'image') {
|
|
73087
|
+
<img [src]="att.url" [alt]="att.file.name" />
|
|
73088
|
+
} @else {
|
|
73089
|
+
<ion-icon name="document-outline" aria-hidden="true" />
|
|
73090
|
+
<span class="thumb-name">{{ att.file.name }}</span>
|
|
73091
|
+
}
|
|
73092
|
+
<button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
|
|
73093
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73094
|
+
</button>
|
|
73095
|
+
</div>
|
|
73096
|
+
}
|
|
73097
|
+
}
|
|
73098
|
+
</div>
|
|
73099
|
+
}
|
|
73100
|
+
|
|
73101
|
+
@if (recording()) {
|
|
73102
|
+
<div class="rec-bar">
|
|
73103
|
+
<button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
|
|
73104
|
+
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
73105
|
+
</button>
|
|
73106
|
+
<span class="rec-dot"></span>
|
|
73107
|
+
<span class="rec-time">{{ recTimeLabel() }}</span>
|
|
73108
|
+
<span class="rec-spacer"></span>
|
|
73109
|
+
<button class="send-btn" [attr.aria-label]="t('done')" (click)="finishRecording()">
|
|
73110
|
+
<ion-icon name="checkmark-outline" aria-hidden="true" />
|
|
73111
|
+
</button>
|
|
73112
|
+
</div>
|
|
73113
|
+
} @else {
|
|
73114
|
+
<div class="pill">
|
|
73115
|
+
@if (showAttach()) {
|
|
73116
|
+
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
73117
|
+
<ion-icon name="add-outline" aria-hidden="true" />
|
|
73118
|
+
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73119
|
+
</label>
|
|
73120
|
+
}
|
|
73121
|
+
|
|
73122
|
+
<ion-textarea
|
|
73123
|
+
class="field"
|
|
73124
|
+
[placeholder]="placeholderText()"
|
|
73125
|
+
[disabled]="disabled()"
|
|
73126
|
+
[maxlength]="maxLength()"
|
|
73127
|
+
[autoGrow]="true"
|
|
73128
|
+
[rows]="1"
|
|
73129
|
+
[value]="body()"
|
|
73130
|
+
[spellcheck]="false"
|
|
73131
|
+
autocapitalize="sentences"
|
|
73132
|
+
autocorrect="off"
|
|
73133
|
+
enterkeyhint="enter"
|
|
73134
|
+
(ionInput)="onInput($event)"
|
|
73135
|
+
(keydown)="onKeydown($event)"
|
|
73136
|
+
(paste)="onPaste($event)"
|
|
73137
|
+
></ion-textarea>
|
|
73138
|
+
|
|
73139
|
+
@if (canSend()) {
|
|
73140
|
+
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73141
|
+
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73142
|
+
</button>
|
|
73143
|
+
} @else if (showMic()) {
|
|
73144
|
+
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
|
|
73145
|
+
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73146
|
+
</button>
|
|
73147
|
+
}
|
|
73148
|
+
</div>
|
|
73149
|
+
}
|
|
73150
|
+
</div>
|
|
73151
|
+
`, 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)}.pending-row{display:flex;gap:8px;flex-wrap:wrap;padding:2px 4px}.thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.thumb img{width:100%;height:100%;object-fit:cover}.thumb.file{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:4px;color:var(--ion-color-medium, #92949c);font-size:1.5rem}.thumb-name{font-size:.5625rem;line-height:1.1;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ion-text-color, #000)}.thumb-remove{position:absolute;top:2px;right:2px;width:20px;height:20px;border:none;border-radius:50%;background:#0000008c;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.875rem}.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{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)}.audio-chip{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border-radius:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.audio-chip audio{flex:1;height:36px;min-width:0}.thumb-remove.inline{position:static;flex-shrink:0;background:var(--ion-color-medium, #92949c)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\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"] }] }); }
|
|
73152
|
+
}
|
|
73153
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
|
|
73154
|
+
type: Component,
|
|
73155
|
+
args: [{ selector: 'val-chat-composer', standalone: true, imports: [CommonModule, IonTextarea, IonIcon], template: `
|
|
73156
|
+
<div class="composer">
|
|
73157
|
+
@if (replyingTo()) {
|
|
73158
|
+
<div class="reply-bar">
|
|
73159
|
+
<div class="reply-content">
|
|
73160
|
+
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
73161
|
+
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
73162
|
+
</div>
|
|
73163
|
+
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
73164
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73165
|
+
</button>
|
|
73166
|
+
</div>
|
|
73167
|
+
}
|
|
73168
|
+
|
|
73169
|
+
@if (pending().length > 0) {
|
|
73170
|
+
<div class="pending-row">
|
|
73171
|
+
@for (att of pending(); track att.id) {
|
|
73172
|
+
@if (att.kind === 'audio') {
|
|
73173
|
+
<div class="audio-chip">
|
|
73174
|
+
<audio controls preload="metadata" [src]="att.url"></audio>
|
|
73175
|
+
<button class="thumb-remove inline" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
|
|
73176
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73177
|
+
</button>
|
|
73178
|
+
</div>
|
|
73179
|
+
} @else {
|
|
73180
|
+
<div class="thumb" [class.file]="att.kind === 'file'">
|
|
73181
|
+
@if (att.kind === 'image') {
|
|
73182
|
+
<img [src]="att.url" [alt]="att.file.name" />
|
|
73183
|
+
} @else {
|
|
73184
|
+
<ion-icon name="document-outline" aria-hidden="true" />
|
|
73185
|
+
<span class="thumb-name">{{ att.file.name }}</span>
|
|
73186
|
+
}
|
|
73187
|
+
<button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
|
|
73188
|
+
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73189
|
+
</button>
|
|
73190
|
+
</div>
|
|
73191
|
+
}
|
|
73192
|
+
}
|
|
73193
|
+
</div>
|
|
73194
|
+
}
|
|
73195
|
+
|
|
73196
|
+
@if (recording()) {
|
|
73197
|
+
<div class="rec-bar">
|
|
73198
|
+
<button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
|
|
73199
|
+
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
73200
|
+
</button>
|
|
73201
|
+
<span class="rec-dot"></span>
|
|
73202
|
+
<span class="rec-time">{{ recTimeLabel() }}</span>
|
|
73203
|
+
<span class="rec-spacer"></span>
|
|
73204
|
+
<button class="send-btn" [attr.aria-label]="t('done')" (click)="finishRecording()">
|
|
73205
|
+
<ion-icon name="checkmark-outline" aria-hidden="true" />
|
|
73206
|
+
</button>
|
|
73207
|
+
</div>
|
|
73208
|
+
} @else {
|
|
73209
|
+
<div class="pill">
|
|
73210
|
+
@if (showAttach()) {
|
|
73211
|
+
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
73212
|
+
<ion-icon name="add-outline" aria-hidden="true" />
|
|
73213
|
+
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73214
|
+
</label>
|
|
73215
|
+
}
|
|
73216
|
+
|
|
73217
|
+
<ion-textarea
|
|
73218
|
+
class="field"
|
|
73219
|
+
[placeholder]="placeholderText()"
|
|
73220
|
+
[disabled]="disabled()"
|
|
73221
|
+
[maxlength]="maxLength()"
|
|
73222
|
+
[autoGrow]="true"
|
|
73223
|
+
[rows]="1"
|
|
73224
|
+
[value]="body()"
|
|
73225
|
+
[spellcheck]="false"
|
|
73226
|
+
autocapitalize="sentences"
|
|
73227
|
+
autocorrect="off"
|
|
73228
|
+
enterkeyhint="enter"
|
|
73229
|
+
(ionInput)="onInput($event)"
|
|
73230
|
+
(keydown)="onKeydown($event)"
|
|
73231
|
+
(paste)="onPaste($event)"
|
|
73232
|
+
></ion-textarea>
|
|
73233
|
+
|
|
73234
|
+
@if (canSend()) {
|
|
73235
|
+
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73236
|
+
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73237
|
+
</button>
|
|
73238
|
+
} @else if (showMic()) {
|
|
73239
|
+
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
|
|
73240
|
+
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73241
|
+
</button>
|
|
73242
|
+
}
|
|
73243
|
+
</div>
|
|
73244
|
+
}
|
|
73245
|
+
</div>
|
|
73246
|
+
`, 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)}.pending-row{display:flex;gap:8px;flex-wrap:wrap;padding:2px 4px}.thumb{position:relative;width:64px;height:64px;border-radius:10px;overflow:hidden;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.thumb img{width:100%;height:100%;object-fit:cover}.thumb.file{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;padding:4px;color:var(--ion-color-medium, #92949c);font-size:1.5rem}.thumb-name{font-size:.5625rem;line-height:1.1;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ion-text-color, #000)}.thumb-remove{position:absolute;top:2px;right:2px;width:20px;height:20px;border:none;border-radius:50%;background:#0000008c;color:#fff;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:.875rem}.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{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)}.audio-chip{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border-radius:12px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.audio-chip audio{flex:1;height:36px;min-width:0}.thumb-remove.inline{position:static;flex-shrink:0;background:var(--ion-color-medium, #92949c)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"] }]
|
|
73247
|
+
}], ctorParameters: () => [] });
|
|
73248
|
+
|
|
73249
|
+
/**
|
|
73250
|
+
* val-conversation-list-item — fila de lista de conversaciones estilo
|
|
73251
|
+
* Telegram/WhatsApp: avatar (imagen o iniciales), nombre + hora relativa,
|
|
73252
|
+
* preview del último mensaje + badge de no-leídos. Signal inputs, dark-safe.
|
|
73253
|
+
*
|
|
73254
|
+
* @example
|
|
73255
|
+
* <val-conversation-list-item
|
|
73256
|
+
* [name]="conv.name"
|
|
73257
|
+
* [lastMessage]="conv.lastMsgPreview"
|
|
73258
|
+
* [timestamp]="conv.lastMsgAt"
|
|
73259
|
+
* [unreadCount]="conv.unread"
|
|
73260
|
+
* (select)="open(conv.convId)" />
|
|
73261
|
+
*/
|
|
73262
|
+
class ConversationListItemComponent {
|
|
73263
|
+
constructor() {
|
|
73264
|
+
this.i18n = inject(I18nService);
|
|
73265
|
+
this.name = input('');
|
|
73266
|
+
this.avatarUrl = input(undefined);
|
|
73267
|
+
this.lastMessage = input('');
|
|
73268
|
+
this.timestamp = input(undefined);
|
|
73269
|
+
this.unreadCount = input(0);
|
|
73270
|
+
this.locale = input('es-CL');
|
|
73271
|
+
this.select = output();
|
|
73272
|
+
this.initials = computed(() => {
|
|
73273
|
+
const name = this.name().trim();
|
|
73274
|
+
if (!name)
|
|
73275
|
+
return '?';
|
|
73276
|
+
const parts = name.split(/\s+/);
|
|
73277
|
+
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
73278
|
+
});
|
|
73279
|
+
this.timeLabel = computed(() => formatRelativeTime(this.timestamp(), this.locale(), {
|
|
73280
|
+
today: '',
|
|
73281
|
+
yesterday: this.i18n.t('yesterday', '_global'),
|
|
73282
|
+
}));
|
|
73283
|
+
this.i18n.registerDefaults('ConversationListItem', {
|
|
73284
|
+
es: { empty: 'Sin mensajes' },
|
|
73285
|
+
en: { empty: 'No messages' },
|
|
73286
|
+
});
|
|
73287
|
+
}
|
|
73288
|
+
emptyText() {
|
|
73289
|
+
this.i18n.lang();
|
|
73290
|
+
return this.i18n.t('empty', 'ConversationListItem');
|
|
73291
|
+
}
|
|
73292
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73293
|
+
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: `
|
|
73294
|
+
<button class="item" (click)="select.emit()">
|
|
73295
|
+
<div class="avatar">
|
|
73296
|
+
@if (avatarUrl()) {
|
|
73297
|
+
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73298
|
+
} @else {
|
|
73299
|
+
<span>{{ initials() }}</span>
|
|
73300
|
+
}
|
|
73301
|
+
</div>
|
|
73302
|
+
<div class="content">
|
|
73303
|
+
<div class="top">
|
|
73304
|
+
<span class="name">{{ name() }}</span>
|
|
73305
|
+
@if (timeLabel()) {
|
|
73306
|
+
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73307
|
+
}
|
|
73308
|
+
</div>
|
|
73309
|
+
<div class="bottom">
|
|
73310
|
+
<span class="preview" [class.muted]="!lastMessage()">
|
|
73311
|
+
{{ lastMessage() || emptyText() }}
|
|
73312
|
+
</span>
|
|
73313
|
+
@if (unreadCount() > 0) {
|
|
73314
|
+
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73315
|
+
}
|
|
73316
|
+
</div>
|
|
73317
|
+
</div>
|
|
73318
|
+
</button>
|
|
73319
|
+
`, 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 }] }); }
|
|
73320
|
+
}
|
|
73321
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, decorators: [{
|
|
73322
|
+
type: Component,
|
|
73323
|
+
args: [{ selector: 'val-conversation-list-item', standalone: true, imports: [CommonModule], template: `
|
|
73324
|
+
<button class="item" (click)="select.emit()">
|
|
73325
|
+
<div class="avatar">
|
|
73326
|
+
@if (avatarUrl()) {
|
|
73327
|
+
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73328
|
+
} @else {
|
|
73329
|
+
<span>{{ initials() }}</span>
|
|
73330
|
+
}
|
|
73331
|
+
</div>
|
|
73332
|
+
<div class="content">
|
|
73333
|
+
<div class="top">
|
|
73334
|
+
<span class="name">{{ name() }}</span>
|
|
73335
|
+
@if (timeLabel()) {
|
|
73336
|
+
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73337
|
+
}
|
|
73338
|
+
</div>
|
|
73339
|
+
<div class="bottom">
|
|
73340
|
+
<span class="preview" [class.muted]="!lastMessage()">
|
|
73341
|
+
{{ lastMessage() || emptyText() }}
|
|
73342
|
+
</span>
|
|
73343
|
+
@if (unreadCount() > 0) {
|
|
73344
|
+
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73345
|
+
}
|
|
73346
|
+
</div>
|
|
73347
|
+
</div>
|
|
73348
|
+
</button>
|
|
73349
|
+
`, 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"] }]
|
|
73350
|
+
}], ctorParameters: () => [] });
|
|
73351
|
+
|
|
72671
73352
|
const TYPING_I18N = {
|
|
72672
73353
|
es: {
|
|
72673
73354
|
one: '{name} esta escribiendo',
|
|
@@ -72683,8 +73364,9 @@ const TYPING_I18N = {
|
|
|
72683
73364
|
/**
|
|
72684
73365
|
* val-typing-indicator
|
|
72685
73366
|
*
|
|
72686
|
-
* Muestra quienes estan escribiendo
|
|
72687
|
-
*
|
|
73367
|
+
* Muestra quienes estan escribiendo con animacion de tres puntos. Se oculta
|
|
73368
|
+
* si `typingUsers` esta vacio. Usa signal input para que `typingText` (computed)
|
|
73369
|
+
* recompute al cambiar la lista.
|
|
72688
73370
|
*
|
|
72689
73371
|
* @example
|
|
72690
73372
|
* <val-typing-indicator [typingUsers]="['Maria', 'Carlos']" />
|
|
@@ -72692,10 +73374,10 @@ const TYPING_I18N = {
|
|
|
72692
73374
|
class TypingIndicatorComponent {
|
|
72693
73375
|
constructor() {
|
|
72694
73376
|
this.i18n = inject(I18nService);
|
|
72695
|
-
this.typingUsers = [];
|
|
73377
|
+
this.typingUsers = input([]);
|
|
72696
73378
|
this.typingText = computed(() => {
|
|
72697
73379
|
this.i18n.lang();
|
|
72698
|
-
const users = this.typingUsers
|
|
73380
|
+
const users = this.typingUsers();
|
|
72699
73381
|
if (users.length === 0)
|
|
72700
73382
|
return '';
|
|
72701
73383
|
if (users.length === 1) {
|
|
@@ -72709,8 +73391,8 @@ class TypingIndicatorComponent {
|
|
|
72709
73391
|
this.i18n.registerDefaults('TypingIndicator', TYPING_I18N);
|
|
72710
73392
|
}
|
|
72711
73393
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72712
|
-
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: `
|
|
72713
|
-
@if (typingUsers
|
|
73394
|
+
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: `
|
|
73395
|
+
@if (typingUsers().length > 0) {
|
|
72714
73396
|
<div class="typing-indicator">
|
|
72715
73397
|
<div class="dots">
|
|
72716
73398
|
<span></span>
|
|
@@ -72725,7 +73407,7 @@ class TypingIndicatorComponent {
|
|
|
72725
73407
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, decorators: [{
|
|
72726
73408
|
type: Component,
|
|
72727
73409
|
args: [{ selector: 'val-typing-indicator', standalone: true, imports: [CommonModule], template: `
|
|
72728
|
-
@if (typingUsers
|
|
73410
|
+
@if (typingUsers().length > 0) {
|
|
72729
73411
|
<div class="typing-indicator">
|
|
72730
73412
|
<div class="dots">
|
|
72731
73413
|
<span></span>
|
|
@@ -72736,270 +73418,277 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72736
73418
|
</div>
|
|
72737
73419
|
}
|
|
72738
73420
|
`, 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"] }]
|
|
72739
|
-
}], ctorParameters: () => []
|
|
72740
|
-
type: Input
|
|
72741
|
-
}] } });
|
|
73421
|
+
}], ctorParameters: () => [] });
|
|
72742
73422
|
|
|
72743
|
-
addIcons({ chatbubblesOutline });
|
|
73423
|
+
addIcons({ arrowDownOutline, chatbubblesOutline });
|
|
72744
73424
|
const CHAT_WINDOW_I18N = {
|
|
72745
73425
|
es: {
|
|
72746
73426
|
conversationClosed: 'Esta conversacion esta cerrada',
|
|
72747
73427
|
loading: 'Cargando mensajes...',
|
|
72748
|
-
noMessages: '
|
|
73428
|
+
noMessages: 'Aun no hay mensajes. Escribe el primero.',
|
|
73429
|
+
newMessages: 'Mensajes nuevos',
|
|
72749
73430
|
},
|
|
72750
73431
|
en: {
|
|
72751
73432
|
conversationClosed: 'This conversation is closed',
|
|
72752
73433
|
loading: 'Loading messages...',
|
|
72753
|
-
noMessages: 'No messages yet',
|
|
73434
|
+
noMessages: 'No messages yet. Send the first one.',
|
|
73435
|
+
newMessages: 'New messages',
|
|
72754
73436
|
},
|
|
72755
73437
|
};
|
|
72756
73438
|
/**
|
|
72757
|
-
* val-chat-window
|
|
73439
|
+
* val-chat-window — ventana de chat completa (signal inputs).
|
|
72758
73440
|
*
|
|
72759
|
-
*
|
|
72760
|
-
*
|
|
72761
|
-
*
|
|
72762
|
-
*
|
|
73441
|
+
* Agrupa mensajes consecutivos del mismo emisor y por día (separadores sticky),
|
|
73442
|
+
* muestra avatar/nombre solo en los bordes del grupo, scroll-to-bottom inteligente
|
|
73443
|
+
* (auto-scroll si estás abajo, pill "mensajes nuevos" si estás leyendo arriba),
|
|
73444
|
+
* composer moderno con contexto de respuesta, y typing indicator.
|
|
72763
73445
|
*
|
|
72764
|
-
*
|
|
72765
|
-
*
|
|
72766
|
-
*
|
|
72767
|
-
* [messages]="messages()"
|
|
72768
|
-
* [currentUserId]="userId"
|
|
72769
|
-
* [isOpen]="conv.status === 'open'"
|
|
72770
|
-
* [typingUsers]="typingUsers()"
|
|
72771
|
-
* (sendMessage)="onSend($event)"
|
|
72772
|
-
* (reactionClick)="onReaction($event)"
|
|
72773
|
-
* />
|
|
73446
|
+
* API pública estable (consumida por thread-panel y showcase): inputs convId,
|
|
73447
|
+
* messages, currentUserId, isOpen, typingUsers, isLoading; outputs sendMessage,
|
|
73448
|
+
* loadMore, reactionClick, deleteMessage, replyTo (+ editMessage/attach/voice nuevos).
|
|
72774
73449
|
*/
|
|
72775
73450
|
class ChatWindowComponent {
|
|
72776
73451
|
constructor() {
|
|
72777
73452
|
this.i18n = inject(I18nService);
|
|
72778
|
-
this.convId = '';
|
|
72779
|
-
this.messages = [];
|
|
72780
|
-
this.
|
|
72781
|
-
this.
|
|
72782
|
-
this.
|
|
72783
|
-
this.
|
|
72784
|
-
this.
|
|
72785
|
-
this.
|
|
72786
|
-
this.
|
|
72787
|
-
this.
|
|
72788
|
-
this.
|
|
72789
|
-
this.
|
|
72790
|
-
this.
|
|
72791
|
-
this.
|
|
72792
|
-
this.
|
|
72793
|
-
|
|
73453
|
+
this.convId = input('');
|
|
73454
|
+
this.messages = input([]);
|
|
73455
|
+
this.currentUserId = input('');
|
|
73456
|
+
this.isOpen = input(true);
|
|
73457
|
+
this.typingUsers = input([]);
|
|
73458
|
+
this.isLoading = input(false);
|
|
73459
|
+
this.sendMessage = output();
|
|
73460
|
+
this.loadMore = output();
|
|
73461
|
+
this.reactionClick = output();
|
|
73462
|
+
this.deleteMessage = output();
|
|
73463
|
+
this.replyTo = output();
|
|
73464
|
+
this.editMessage = output();
|
|
73465
|
+
this.typing = output();
|
|
73466
|
+
this.voice = output();
|
|
73467
|
+
this.msgsEl = viewChild('msgs');
|
|
73468
|
+
this.replyingTo = signal(null);
|
|
73469
|
+
this.atBottom = signal(true);
|
|
73470
|
+
this.hasNew = signal(false);
|
|
73471
|
+
this.locale = computed(() => (this.i18n.lang() === 'en' ? 'en-US' : 'es-CL'));
|
|
73472
|
+
this.rows = computed(() => {
|
|
73473
|
+
const msgs = this.messages();
|
|
72794
73474
|
if (msgs.length === 0)
|
|
72795
73475
|
return [];
|
|
72796
|
-
const
|
|
72797
|
-
|
|
72798
|
-
|
|
72799
|
-
|
|
72800
|
-
|
|
72801
|
-
|
|
72802
|
-
|
|
72803
|
-
|
|
72804
|
-
|
|
72805
|
-
|
|
72806
|
-
|
|
72807
|
-
|
|
72808
|
-
|
|
72809
|
-
|
|
73476
|
+
const labels = {
|
|
73477
|
+
today: this.i18n.t('today', '_global'),
|
|
73478
|
+
yesterday: this.i18n.t('yesterday', '_global'),
|
|
73479
|
+
};
|
|
73480
|
+
const out = [];
|
|
73481
|
+
let prevDay = '';
|
|
73482
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
73483
|
+
const m = msgs[i];
|
|
73484
|
+
const d = m.createdAt instanceof Date ? m.createdAt : new Date(m.createdAt);
|
|
73485
|
+
const day = `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}`;
|
|
73486
|
+
const newDay = day !== prevDay;
|
|
73487
|
+
if (newDay) {
|
|
73488
|
+
out.push({ kind: 'date', id: 'date-' + day, label: formatDateSeparator(d, this.locale(), labels) });
|
|
73489
|
+
prevDay = day;
|
|
72810
73490
|
}
|
|
73491
|
+
const prev = msgs[i - 1];
|
|
73492
|
+
const next = msgs[i + 1];
|
|
73493
|
+
const firstInGroup = newDay || !prev || prev.senderId !== m.senderId;
|
|
73494
|
+
const nextDay = next
|
|
73495
|
+
? `${new Date(next.createdAt).getFullYear()}-${new Date(next.createdAt).getMonth()}-${new Date(next.createdAt).getDate()}`
|
|
73496
|
+
: '';
|
|
73497
|
+
const lastInGroup = !next || next.senderId !== m.senderId || nextDay !== day;
|
|
73498
|
+
out.push({
|
|
73499
|
+
kind: 'msg',
|
|
73500
|
+
id: m.msgId,
|
|
73501
|
+
msg: m,
|
|
73502
|
+
showName: firstInGroup && !m.isMine,
|
|
73503
|
+
showAvatar: lastInGroup && !m.isMine,
|
|
73504
|
+
tail: lastInGroup,
|
|
73505
|
+
});
|
|
72811
73506
|
}
|
|
72812
|
-
|
|
72813
|
-
groups.push({ dateLabel: currentLabel, messages: currentGroup });
|
|
72814
|
-
}
|
|
72815
|
-
return groups;
|
|
72816
|
-
});
|
|
72817
|
-
this.closedText = computed(() => {
|
|
72818
|
-
this.i18n.lang();
|
|
72819
|
-
return this.i18n.t('conversationClosed', 'ChatWindow');
|
|
72820
|
-
});
|
|
72821
|
-
this.loadingText = computed(() => {
|
|
72822
|
-
this.i18n.lang();
|
|
72823
|
-
return this.i18n.t('loading', 'ChatWindow');
|
|
73507
|
+
return out;
|
|
72824
73508
|
});
|
|
72825
73509
|
this.i18n.registerDefaults('ChatWindow', CHAT_WINDOW_I18N);
|
|
72826
|
-
|
|
72827
|
-
|
|
72828
|
-
|
|
72829
|
-
|
|
72830
|
-
|
|
72831
|
-
|
|
72832
|
-
|
|
72833
|
-
|
|
72834
|
-
|
|
72835
|
-
|
|
72836
|
-
|
|
72837
|
-
|
|
72838
|
-
|
|
72839
|
-
|
|
72840
|
-
if (changes['messages']) {
|
|
72841
|
-
const current = changes['messages'].currentValue ?? [];
|
|
72842
|
-
if (current.length !== this.prevMessageCount) {
|
|
72843
|
-
this.prevMessageCount = current.length;
|
|
72844
|
-
this.shouldScrollToBottom = true;
|
|
73510
|
+
// Auto-scroll al fondo cuando llegan mensajes y el usuario ya estaba abajo
|
|
73511
|
+
// (o el último mensaje es propio). Si está leyendo arriba, marca "nuevos".
|
|
73512
|
+
let prevCount = 0;
|
|
73513
|
+
effect(() => {
|
|
73514
|
+
const rows = this.rows();
|
|
73515
|
+
const count = rows.length;
|
|
73516
|
+
if (count === prevCount)
|
|
73517
|
+
return;
|
|
73518
|
+
const grew = count > prevCount;
|
|
73519
|
+
prevCount = count;
|
|
73520
|
+
const last = this.messages()[this.messages().length - 1];
|
|
73521
|
+
const mine = last?.isMine ?? false;
|
|
73522
|
+
if (this.atBottom() || mine) {
|
|
73523
|
+
setTimeout(() => this.scrollToBottom(false), 0);
|
|
72845
73524
|
}
|
|
72846
|
-
|
|
72847
|
-
|
|
72848
|
-
|
|
72849
|
-
|
|
72850
|
-
this.scrollToBottom();
|
|
72851
|
-
this.shouldScrollToBottom = false;
|
|
72852
|
-
}
|
|
73525
|
+
else if (grew) {
|
|
73526
|
+
this.hasNew.set(true);
|
|
73527
|
+
}
|
|
73528
|
+
});
|
|
72853
73529
|
}
|
|
72854
|
-
|
|
72855
|
-
const el = this.
|
|
72856
|
-
if (!el
|
|
73530
|
+
onScroll() {
|
|
73531
|
+
const el = this.msgsEl()?.nativeElement;
|
|
73532
|
+
if (!el)
|
|
72857
73533
|
return;
|
|
72858
|
-
const
|
|
72859
|
-
|
|
72860
|
-
if (
|
|
72861
|
-
|
|
72862
|
-
|
|
72863
|
-
|
|
72864
|
-
onReactionClick(event) {
|
|
72865
|
-
this.reactionClick.emit({ msgId: event.msgId, token: event.token });
|
|
73534
|
+
const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
73535
|
+
this.atBottom.set(nearBottom);
|
|
73536
|
+
if (nearBottom)
|
|
73537
|
+
this.hasNew.set(false);
|
|
73538
|
+
if (el.scrollTop < 40)
|
|
73539
|
+
this.loadMore.emit();
|
|
72866
73540
|
}
|
|
72867
|
-
|
|
72868
|
-
|
|
73541
|
+
scrollToBottom(smooth) {
|
|
73542
|
+
const el = this.msgsEl()?.nativeElement;
|
|
73543
|
+
if (!el)
|
|
73544
|
+
return;
|
|
73545
|
+
el.scrollTo({ top: el.scrollHeight, behavior: smooth ? 'smooth' : 'auto' });
|
|
73546
|
+
this.atBottom.set(true);
|
|
73547
|
+
this.hasNew.set(false);
|
|
72869
73548
|
}
|
|
72870
|
-
|
|
72871
|
-
|
|
73549
|
+
onSend(event) {
|
|
73550
|
+
this.sendMessage.emit(event);
|
|
73551
|
+
this.replyingTo.set(null);
|
|
72872
73552
|
}
|
|
72873
|
-
|
|
72874
|
-
|
|
72875
|
-
|
|
72876
|
-
|
|
73553
|
+
onAction(event) {
|
|
73554
|
+
switch (event.type) {
|
|
73555
|
+
case 'reply': {
|
|
73556
|
+
const m = this.messages().find(x => x.msgId === event.msgId);
|
|
73557
|
+
if (m) {
|
|
73558
|
+
this.replyingTo.set({ msgId: m.msgId, senderName: m.senderName, body: m.body });
|
|
73559
|
+
}
|
|
73560
|
+
this.replyTo.emit(event.msgId);
|
|
73561
|
+
break;
|
|
73562
|
+
}
|
|
73563
|
+
case 'edit':
|
|
73564
|
+
this.editMessage.emit(event.msgId);
|
|
73565
|
+
break;
|
|
73566
|
+
case 'delete':
|
|
73567
|
+
this.deleteMessage.emit(event.msgId);
|
|
73568
|
+
break;
|
|
73569
|
+
case 'react':
|
|
73570
|
+
this.reactionClick.emit({ msgId: event.msgId, token: event.token ?? '👍' });
|
|
73571
|
+
break;
|
|
72877
73572
|
}
|
|
72878
73573
|
}
|
|
72879
|
-
|
|
72880
|
-
|
|
72881
|
-
return '';
|
|
72882
|
-
const d = date instanceof Date ? date : new Date(date);
|
|
72883
|
-
const now = new Date();
|
|
72884
|
-
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
72885
|
-
const msgDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
72886
|
-
const diffMs = today.getTime() - msgDay.getTime();
|
|
72887
|
-
const diffDays = Math.round(diffMs / 86400000);
|
|
72888
|
-
if (diffDays === 0)
|
|
72889
|
-
return this.i18n.t('today', '_global') || 'Hoy';
|
|
72890
|
-
if (diffDays === 1)
|
|
72891
|
-
return this.i18n.t('yesterday', '_global') || 'Ayer';
|
|
72892
|
-
return d.toLocaleDateString([], { day: '2-digit', month: 'short', year: 'numeric' });
|
|
73574
|
+
t(key) {
|
|
73575
|
+
return this.i18n.t(key, 'ChatWindow');
|
|
72893
73576
|
}
|
|
72894
73577
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72895
|
-
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",
|
|
72896
|
-
<div class="chat
|
|
72897
|
-
<div class="messages
|
|
72898
|
-
@if (isLoading) {
|
|
72899
|
-
<div class="
|
|
72900
|
-
<
|
|
72901
|
-
|
|
73578
|
+
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", voice: "voice" }, viewQueries: [{ propertyName: "msgsEl", first: true, predicate: ["msgs"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
73579
|
+
<div class="chat">
|
|
73580
|
+
<div #msgs class="messages" (scroll)="onScroll()">
|
|
73581
|
+
@if (isLoading()) {
|
|
73582
|
+
<div class="state">
|
|
73583
|
+
<span>{{ t('loading') }}</span>
|
|
73584
|
+
</div>
|
|
73585
|
+
} @else if (rows().length === 0) {
|
|
73586
|
+
<div class="state empty">
|
|
73587
|
+
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73588
|
+
<span>{{ t('noMessages') }}</span>
|
|
72902
73589
|
</div>
|
|
72903
73590
|
} @else {
|
|
72904
|
-
@for (
|
|
72905
|
-
|
|
72906
|
-
<div class="date-
|
|
72907
|
-
|
|
72908
|
-
|
|
72909
|
-
|
|
72910
|
-
@for (msg of group.messages; track msg.msgId) {
|
|
73591
|
+
@for (row of rows(); track row.id) {
|
|
73592
|
+
@if (row.kind === 'date') {
|
|
73593
|
+
<div class="date-sep">
|
|
73594
|
+
<span>{{ row.label }}</span>
|
|
73595
|
+
</div>
|
|
73596
|
+
} @else {
|
|
72911
73597
|
<val-message-bubble
|
|
72912
|
-
[
|
|
72913
|
-
|
|
72914
|
-
|
|
72915
|
-
|
|
72916
|
-
|
|
73598
|
+
[msg]="row.msg"
|
|
73599
|
+
[showName]="row.showName"
|
|
73600
|
+
[showAvatar]="row.showAvatar"
|
|
73601
|
+
[tail]="row.tail"
|
|
73602
|
+
[locale]="locale()"
|
|
73603
|
+
(action)="onAction($event)"
|
|
72917
73604
|
/>
|
|
72918
73605
|
}
|
|
72919
73606
|
}
|
|
72920
73607
|
}
|
|
73608
|
+
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
72921
73609
|
</div>
|
|
72922
73610
|
|
|
72923
|
-
|
|
73611
|
+
@if (!atBottom()) {
|
|
73612
|
+
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73613
|
+
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73614
|
+
@if (hasNew()) {
|
|
73615
|
+
<span class="dot"></span>
|
|
73616
|
+
}
|
|
73617
|
+
</button>
|
|
73618
|
+
}
|
|
72924
73619
|
|
|
72925
|
-
@if (
|
|
72926
|
-
<
|
|
73620
|
+
@if (isOpen()) {
|
|
73621
|
+
<val-chat-composer
|
|
73622
|
+
[replyingTo]="replyingTo()"
|
|
73623
|
+
(send)="onSend($event)"
|
|
73624
|
+
(typing)="typing.emit()"
|
|
73625
|
+
(voice)="voice.emit()"
|
|
73626
|
+
(cancelReply)="replyingTo.set(null)"
|
|
73627
|
+
/>
|
|
72927
73628
|
} @else {
|
|
72928
|
-
<
|
|
73629
|
+
<div class="closed-banner">{{ t('conversationClosed') }}</div>
|
|
72929
73630
|
}
|
|
72930
73631
|
</div>
|
|
72931
|
-
`, isInline: true, styles: [".chat
|
|
73632
|
+
`, 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", "showMic"], outputs: ["send", "typing", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
|
|
72932
73633
|
}
|
|
72933
73634
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
|
|
72934
73635
|
type: Component,
|
|
72935
|
-
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon,
|
|
72936
|
-
<div class="chat
|
|
72937
|
-
<div class="messages
|
|
72938
|
-
@if (isLoading) {
|
|
72939
|
-
<div class="
|
|
72940
|
-
<
|
|
72941
|
-
|
|
73636
|
+
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon, MessageBubbleComponent, ChatComposerComponent, TypingIndicatorComponent], template: `
|
|
73637
|
+
<div class="chat">
|
|
73638
|
+
<div #msgs class="messages" (scroll)="onScroll()">
|
|
73639
|
+
@if (isLoading()) {
|
|
73640
|
+
<div class="state">
|
|
73641
|
+
<span>{{ t('loading') }}</span>
|
|
73642
|
+
</div>
|
|
73643
|
+
} @else if (rows().length === 0) {
|
|
73644
|
+
<div class="state empty">
|
|
73645
|
+
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73646
|
+
<span>{{ t('noMessages') }}</span>
|
|
72942
73647
|
</div>
|
|
72943
73648
|
} @else {
|
|
72944
|
-
@for (
|
|
72945
|
-
|
|
72946
|
-
<div class="date-
|
|
72947
|
-
|
|
72948
|
-
|
|
72949
|
-
|
|
72950
|
-
@for (msg of group.messages; track msg.msgId) {
|
|
73649
|
+
@for (row of rows(); track row.id) {
|
|
73650
|
+
@if (row.kind === 'date') {
|
|
73651
|
+
<div class="date-sep">
|
|
73652
|
+
<span>{{ row.label }}</span>
|
|
73653
|
+
</div>
|
|
73654
|
+
} @else {
|
|
72951
73655
|
<val-message-bubble
|
|
72952
|
-
[
|
|
72953
|
-
|
|
72954
|
-
|
|
72955
|
-
|
|
72956
|
-
|
|
73656
|
+
[msg]="row.msg"
|
|
73657
|
+
[showName]="row.showName"
|
|
73658
|
+
[showAvatar]="row.showAvatar"
|
|
73659
|
+
[tail]="row.tail"
|
|
73660
|
+
[locale]="locale()"
|
|
73661
|
+
(action)="onAction($event)"
|
|
72957
73662
|
/>
|
|
72958
73663
|
}
|
|
72959
73664
|
}
|
|
72960
73665
|
}
|
|
73666
|
+
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
72961
73667
|
</div>
|
|
72962
73668
|
|
|
72963
|
-
|
|
73669
|
+
@if (!atBottom()) {
|
|
73670
|
+
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73671
|
+
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73672
|
+
@if (hasNew()) {
|
|
73673
|
+
<span class="dot"></span>
|
|
73674
|
+
}
|
|
73675
|
+
</button>
|
|
73676
|
+
}
|
|
72964
73677
|
|
|
72965
|
-
@if (
|
|
72966
|
-
<
|
|
73678
|
+
@if (isOpen()) {
|
|
73679
|
+
<val-chat-composer
|
|
73680
|
+
[replyingTo]="replyingTo()"
|
|
73681
|
+
(send)="onSend($event)"
|
|
73682
|
+
(typing)="typing.emit()"
|
|
73683
|
+
(voice)="voice.emit()"
|
|
73684
|
+
(cancelReply)="replyingTo.set(null)"
|
|
73685
|
+
/>
|
|
72967
73686
|
} @else {
|
|
72968
|
-
<
|
|
73687
|
+
<div class="closed-banner">{{ t('conversationClosed') }}</div>
|
|
72969
73688
|
}
|
|
72970
73689
|
</div>
|
|
72971
|
-
`, styles: [".chat
|
|
72972
|
-
}], ctorParameters: () => []
|
|
72973
|
-
type: Input
|
|
72974
|
-
}], messages: [{
|
|
72975
|
-
type: Input
|
|
72976
|
-
}], participants: [{
|
|
72977
|
-
type: Input
|
|
72978
|
-
}], currentUserId: [{
|
|
72979
|
-
type: Input
|
|
72980
|
-
}], isOpen: [{
|
|
72981
|
-
type: Input
|
|
72982
|
-
}], typingUsers: [{
|
|
72983
|
-
type: Input
|
|
72984
|
-
}], isLoading: [{
|
|
72985
|
-
type: Input
|
|
72986
|
-
}], sendMessage: [{
|
|
72987
|
-
type: Output
|
|
72988
|
-
}], loadMore: [{
|
|
72989
|
-
type: Output
|
|
72990
|
-
}], reactionClick: [{
|
|
72991
|
-
type: Output
|
|
72992
|
-
}], deleteMessage: [{
|
|
72993
|
-
type: Output
|
|
72994
|
-
}], replyTo: [{
|
|
72995
|
-
type: Output
|
|
72996
|
-
}], messagesListRef: [{
|
|
72997
|
-
type: ViewChild,
|
|
72998
|
-
args: ['messagesList']
|
|
72999
|
-
}], chatWindowElRef: [{
|
|
73000
|
-
type: ViewChild,
|
|
73001
|
-
args: ['chatWindowEl']
|
|
73002
|
-
}] } });
|
|
73690
|
+
`, 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"] }]
|
|
73691
|
+
}], ctorParameters: () => [] });
|
|
73003
73692
|
|
|
73004
73693
|
const THREAD_PANEL_I18N = {
|
|
73005
73694
|
es: {
|
|
@@ -73042,7 +73731,7 @@ class ThreadPanelComponent {
|
|
|
73042
73731
|
this.conv = signal(null);
|
|
73043
73732
|
this.messages = signal([]);
|
|
73044
73733
|
this.typingUsers = signal([]);
|
|
73045
|
-
this.isConvOpen = computed(() => this.conv()?.
|
|
73734
|
+
this.isConvOpen = computed(() => this.conv()?.settings?.isOpen === true);
|
|
73046
73735
|
this.loadingText = computed(() => {
|
|
73047
73736
|
this.i18n.lang();
|
|
73048
73737
|
return this.i18n.t('loading', 'ThreadPanel');
|
|
@@ -73080,10 +73769,10 @@ class ThreadPanelComponent {
|
|
|
73080
73769
|
const appId = this.chatConfig?.appId ?? '';
|
|
73081
73770
|
if (orgId && appId) {
|
|
73082
73771
|
this.msgSub = this.conversationSvc
|
|
73083
|
-
.watchMessages(conversation.
|
|
73772
|
+
.watchMessages(conversation.convId, orgId, appId)
|
|
73084
73773
|
.subscribe(msgs => this.messages.set(msgs));
|
|
73085
73774
|
this.typingSub = this.conversationSvc
|
|
73086
|
-
.watchTyping(conversation.
|
|
73775
|
+
.watchTyping(conversation.convId, orgId, appId)
|
|
73087
73776
|
.subscribe(users => this.typingUsers.set(users));
|
|
73088
73777
|
}
|
|
73089
73778
|
}
|
|
@@ -73101,14 +73790,16 @@ class ThreadPanelComponent {
|
|
|
73101
73790
|
const orgId = this.getOrgId();
|
|
73102
73791
|
const appId = this.chatConfig?.appId ?? '';
|
|
73103
73792
|
if (conv && orgId && appId && this.conversationSvc) {
|
|
73104
|
-
this.conversationSvc.stopTyping(conv.
|
|
73793
|
+
this.conversationSvc.stopTyping(conv.convId, orgId, appId);
|
|
73105
73794
|
}
|
|
73106
73795
|
}
|
|
73107
|
-
onSend(
|
|
73796
|
+
onSend(event) {
|
|
73108
73797
|
const conv = this.conv();
|
|
73109
73798
|
if (!conv || !this.conversationSvc)
|
|
73110
73799
|
return;
|
|
73111
|
-
|
|
73800
|
+
// thread-panel no tiene pipeline de subida de adjuntos; solo envía el texto.
|
|
73801
|
+
if (event.text)
|
|
73802
|
+
this.conversationSvc.sendMessage(conv.convId, event.text).subscribe();
|
|
73112
73803
|
}
|
|
73113
73804
|
onReactionClick(event) {
|
|
73114
73805
|
// Reacciones se implementan a nivel de app consumer
|
|
@@ -73118,7 +73809,7 @@ class ThreadPanelComponent {
|
|
|
73118
73809
|
const conv = this.conv();
|
|
73119
73810
|
if (!conv || !this.conversationSvc)
|
|
73120
73811
|
return;
|
|
73121
|
-
this.conversationSvc.deleteMessage(conv.
|
|
73812
|
+
this.conversationSvc.deleteMessage(conv.convId, msgId).subscribe();
|
|
73122
73813
|
}
|
|
73123
73814
|
onReplyTo(_msgId) {
|
|
73124
73815
|
// Implementacion futura: seleccionar mensaje de respuesta
|
|
@@ -73143,7 +73834,7 @@ class ThreadPanelComponent {
|
|
|
73143
73834
|
} @else {
|
|
73144
73835
|
<div class="thread-chat">
|
|
73145
73836
|
<val-chat-window
|
|
73146
|
-
[convId]="conv()?.
|
|
73837
|
+
[convId]="conv()?.convId ?? ''"
|
|
73147
73838
|
[messages]="bubbleMessages()"
|
|
73148
73839
|
[currentUserId]="currentUserId"
|
|
73149
73840
|
[isOpen]="isConvOpen()"
|
|
@@ -73157,7 +73848,7 @@ class ThreadPanelComponent {
|
|
|
73157
73848
|
</div>
|
|
73158
73849
|
}
|
|
73159
73850
|
</div>
|
|
73160
|
-
`, 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", "
|
|
73851
|
+
`, 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", "voice"] }] }); }
|
|
73161
73852
|
}
|
|
73162
73853
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThreadPanelComponent, decorators: [{
|
|
73163
73854
|
type: Component,
|
|
@@ -73177,7 +73868,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73177
73868
|
} @else {
|
|
73178
73869
|
<div class="thread-chat">
|
|
73179
73870
|
<val-chat-window
|
|
73180
|
-
[convId]="conv()?.
|
|
73871
|
+
[convId]="conv()?.convId ?? ''"
|
|
73181
73872
|
[messages]="bubbleMessages()"
|
|
73182
73873
|
[currentUserId]="currentUserId"
|
|
73183
73874
|
[isOpen]="isConvOpen()"
|
|
@@ -73211,5 +73902,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73211
73902
|
* Generated bundle index. Do not edit.
|
|
73212
73903
|
*/
|
|
73213
73904
|
|
|
73214
|
-
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 };
|
|
73905
|
+
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 };
|
|
73215
73906
|
//# sourceMappingURL=valtech-components.mjs.map
|