valtech-components 4.0.226 → 4.0.228
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-input/chat-input.component.mjs +9 -1
- package/esm2022/lib/components/molecules/message-bubble/message-bubble.component.mjs +191 -259
- package/esm2022/lib/components/molecules/typing-indicator/typing-indicator.component.mjs +12 -11
- package/esm2022/lib/components/organisms/chat-window/chat-window.component.mjs +203 -216
- package/esm2022/lib/components/organisms/thread-panel/thread-panel.component.mjs +10 -10
- package/esm2022/lib/services/chat/conversation.service.mjs +11 -23
- package/esm2022/lib/services/chat/types.mjs +1 -1
- package/esm2022/lib/services/i18n/default-content.mjs +1 -5
- package/esm2022/lib/shared/utils/datetime.mjs +1 -54
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +2 -4
- package/fesm2022/valtech-components.mjs +425 -898
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/message-bubble/message-bubble.component.d.ts +32 -33
- package/lib/components/molecules/typing-indicator/typing-indicator.component.d.ts +4 -5
- package/lib/components/organisms/chat-window/chat-window.component.d.ts +56 -52
- package/lib/services/chat/conversation.service.d.ts +2 -7
- package/lib/services/chat/types.d.ts +52 -86
- package/lib/shared/utils/datetime.d.ts +0 -19
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +1 -3
- package/src/lib/services/firebase/firebase-messaging-sw.js +125 -70
- package/esm2022/lib/components/molecules/chat-composer/chat-composer.component.mjs +0 -238
- package/esm2022/lib/components/molecules/conversation-list-item/conversation-list-item.component.mjs +0 -108
- package/lib/components/molecules/chat-composer/chat-composer.component.d.ts +0 -46
- package/lib/components/molecules/conversation-list-item/conversation-list-item.component.d.ts +0 -30
|
@@ -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, returnDownBackOutline } 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.228';
|
|
60
60
|
|
|
61
61
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
62
62
|
let isRefreshing = false;
|
|
@@ -1538,59 +1538,6 @@ 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
|
-
}
|
|
1594
1541
|
|
|
1595
1542
|
/**
|
|
1596
1543
|
* Servicio de internacionalización basado en Angular Signals.
|
|
@@ -7083,8 +7030,6 @@ const VALTECH_DEFAULT_CONTENT = {
|
|
|
7083
7030
|
loading: 'Cargando...',
|
|
7084
7031
|
search: 'Buscar',
|
|
7085
7032
|
learnMore: 'Saber más',
|
|
7086
|
-
today: 'Hoy',
|
|
7087
|
-
yesterday: 'Ayer',
|
|
7088
7033
|
// Estados
|
|
7089
7034
|
success: 'Éxito',
|
|
7090
7035
|
error: 'Error',
|
|
@@ -7242,8 +7187,6 @@ const VALTECH_DEFAULT_CONTENT = {
|
|
|
7242
7187
|
loading: 'Loading...',
|
|
7243
7188
|
search: 'Search',
|
|
7244
7189
|
learnMore: 'Learn more',
|
|
7245
|
-
today: 'Today',
|
|
7246
|
-
yesterday: 'Yesterday',
|
|
7247
7190
|
// States
|
|
7248
7191
|
success: 'Success',
|
|
7249
7192
|
error: 'Error',
|
|
@@ -72097,29 +72040,21 @@ class ConversationService {
|
|
|
72097
72040
|
}
|
|
72098
72041
|
/**
|
|
72099
72042
|
* Crea una nueva conversacion.
|
|
72100
|
-
* El backend responde con un envelope { operationId, conversation } — lo
|
|
72101
|
-
* desenvolvemos para devolver la Conversation directa.
|
|
72102
72043
|
*/
|
|
72103
72044
|
createConversation(req) {
|
|
72104
|
-
return this.http
|
|
72105
|
-
.post(`${this.baseUrl}/conversations`, req)
|
|
72106
|
-
.pipe(map(r => r.conversation));
|
|
72045
|
+
return this.http.post(`${this.baseUrl}/conversations`, req);
|
|
72107
72046
|
}
|
|
72108
72047
|
/**
|
|
72109
|
-
* Obtiene o crea una conversacion asociada a una entidad (post, request,
|
|
72048
|
+
* Obtiene o crea una conversacion asociada a una entidad (post, request, etc.).
|
|
72110
72049
|
* Usa el patron upsert del backend: si ya existe una conversacion para esa entidad,
|
|
72111
72050
|
* la devuelve; si no, la crea.
|
|
72112
|
-
*
|
|
72113
|
-
* El backend espera { entityRef: { type, id }, type } y responde con un envelope
|
|
72114
|
-
* { operationId, conversation, created }.
|
|
72115
72051
|
*/
|
|
72116
|
-
getOrCreateForEntity(entityType, entityId, type = '
|
|
72117
|
-
return this.http
|
|
72118
|
-
|
|
72119
|
-
|
|
72052
|
+
getOrCreateForEntity(entityType, entityId, type = 'entity') {
|
|
72053
|
+
return this.http.post(`${this.baseUrl}/conversations/entity-conversations`, {
|
|
72054
|
+
entityType,
|
|
72055
|
+
entityId,
|
|
72120
72056
|
type,
|
|
72121
|
-
})
|
|
72122
|
-
.pipe(map(r => r.conversation));
|
|
72057
|
+
});
|
|
72123
72058
|
}
|
|
72124
72059
|
/**
|
|
72125
72060
|
* Lista las conversaciones del usuario autenticado.
|
|
@@ -72134,17 +72069,13 @@ class ConversationService {
|
|
|
72134
72069
|
* Obtiene una conversacion por ID.
|
|
72135
72070
|
*/
|
|
72136
72071
|
getConversation(convId) {
|
|
72137
|
-
return this.http
|
|
72138
|
-
.get(`${this.baseUrl}/conversations/${convId}`)
|
|
72139
|
-
.pipe(map(r => r.conversation));
|
|
72072
|
+
return this.http.get(`${this.baseUrl}/conversations/${convId}`);
|
|
72140
72073
|
}
|
|
72141
72074
|
/**
|
|
72142
|
-
* Actualiza el
|
|
72075
|
+
* Actualiza el titulo o estado de una conversacion.
|
|
72143
72076
|
*/
|
|
72144
72077
|
updateConversation(convId, req) {
|
|
72145
|
-
return this.http
|
|
72146
|
-
.patch(`${this.baseUrl}/conversations/${convId}`, req)
|
|
72147
|
-
.pipe(map(r => r.conversation));
|
|
72078
|
+
return this.http.patch(`${this.baseUrl}/conversations/${convId}`, req);
|
|
72148
72079
|
}
|
|
72149
72080
|
/**
|
|
72150
72081
|
* Abre una conversacion cerrada.
|
|
@@ -72343,308 +72274,241 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72343
72274
|
*/
|
|
72344
72275
|
|
|
72345
72276
|
addIcons({
|
|
72346
|
-
alertCircle,
|
|
72347
72277
|
checkmark,
|
|
72348
72278
|
checkmarkDone,
|
|
72349
|
-
createOutline,
|
|
72350
|
-
documentOutline,
|
|
72351
|
-
happyOutline,
|
|
72352
|
-
arrowUndoOutline,
|
|
72353
72279
|
timeOutline,
|
|
72354
|
-
|
|
72280
|
+
alertCircle,
|
|
72281
|
+
pencil,
|
|
72282
|
+
trash,
|
|
72283
|
+
returnDownBackOutline,
|
|
72355
72284
|
});
|
|
72356
|
-
const
|
|
72285
|
+
const CHAT_I18N = {
|
|
72357
72286
|
es: {
|
|
72287
|
+
messageDeleted: '[mensaje eliminado]',
|
|
72288
|
+
edited: '(editado)',
|
|
72289
|
+
react: 'Reaccionar',
|
|
72358
72290
|
reply: 'Responder',
|
|
72359
72291
|
edit: 'Editar',
|
|
72360
72292
|
delete: 'Eliminar',
|
|
72361
|
-
react: 'Reaccionar',
|
|
72362
|
-
deleted: 'Mensaje eliminado',
|
|
72363
|
-
edited: 'editado',
|
|
72364
72293
|
},
|
|
72365
72294
|
en: {
|
|
72295
|
+
messageDeleted: '[message deleted]',
|
|
72296
|
+
edited: '(edited)',
|
|
72297
|
+
react: 'React',
|
|
72366
72298
|
reply: 'Reply',
|
|
72367
72299
|
edit: 'Edit',
|
|
72368
72300
|
delete: 'Delete',
|
|
72369
|
-
react: 'React',
|
|
72370
|
-
deleted: 'Message deleted',
|
|
72371
|
-
edited: 'edited',
|
|
72372
72301
|
},
|
|
72373
72302
|
};
|
|
72374
72303
|
/**
|
|
72375
|
-
* val-message-bubble
|
|
72304
|
+
* val-message-bubble
|
|
72376
72305
|
*
|
|
72377
|
-
*
|
|
72378
|
-
*
|
|
72379
|
-
*
|
|
72380
|
-
* la burbuja (móvil), y se emiten por un único `(action)` tipado.
|
|
72306
|
+
* Burbuja de mensaje para el chat. Alineada a la derecha si isMine, a la izquierda si no.
|
|
72307
|
+
* Muestra nombre del emisor, cuerpo, timestamp, estado de envio, reacciones y acciones.
|
|
72308
|
+
* Si el mensaje fue eliminado muestra un texto en gris sin acciones.
|
|
72381
72309
|
*
|
|
72382
|
-
*
|
|
72383
|
-
*
|
|
72310
|
+
* @example
|
|
72311
|
+
* <val-message-bubble
|
|
72312
|
+
* [props]="msg"
|
|
72313
|
+
* (reactionClick)="onReaction($event)"
|
|
72314
|
+
* (replyClick)="onReply($event)"
|
|
72315
|
+
* (deleteClick)="onDelete($event)"
|
|
72316
|
+
* />
|
|
72384
72317
|
*/
|
|
72385
72318
|
class MessageBubbleComponent {
|
|
72386
72319
|
constructor() {
|
|
72387
72320
|
this.i18n = inject(I18nService);
|
|
72388
|
-
this.
|
|
72389
|
-
this.
|
|
72390
|
-
this.
|
|
72391
|
-
this.
|
|
72392
|
-
this.
|
|
72393
|
-
|
|
72394
|
-
|
|
72395
|
-
this.time = computed(() => formatClockTime(this.msg().createdAt, this.locale()));
|
|
72396
|
-
this.initials = computed(() => {
|
|
72397
|
-
const name = this.msg().senderName?.trim() ?? '';
|
|
72398
|
-
if (!name)
|
|
72399
|
-
return '?';
|
|
72400
|
-
const parts = name.split(/\s+/);
|
|
72401
|
-
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
72402
|
-
});
|
|
72403
|
-
this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
|
|
72404
|
-
this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/')));
|
|
72405
|
-
this.statusIcon = computed(() => {
|
|
72406
|
-
switch (this.msg().status) {
|
|
72407
|
-
case 'sending':
|
|
72408
|
-
return 'time-outline';
|
|
72409
|
-
case 'failed':
|
|
72410
|
-
return 'alert-circle';
|
|
72411
|
-
case 'delivered':
|
|
72412
|
-
case 'read':
|
|
72413
|
-
return 'checkmark-done';
|
|
72414
|
-
case 'sent':
|
|
72415
|
-
default:
|
|
72416
|
-
return 'checkmark';
|
|
72417
|
-
}
|
|
72418
|
-
});
|
|
72419
|
-
this.i18n.registerDefaults('MessageBubble', MESSAGE_BUBBLE_I18N);
|
|
72321
|
+
this.reactionClick = new EventEmitter();
|
|
72322
|
+
this.replyClick = new EventEmitter();
|
|
72323
|
+
this.deleteClick = new EventEmitter();
|
|
72324
|
+
this.editClick = new EventEmitter();
|
|
72325
|
+
if (!this.i18n.hasNamespace('Chat')) {
|
|
72326
|
+
this.i18n.registerDefaults('Chat', CHAT_I18N);
|
|
72327
|
+
}
|
|
72420
72328
|
}
|
|
72421
|
-
|
|
72422
|
-
|
|
72423
|
-
|
|
72424
|
-
this.
|
|
72329
|
+
ngOnInit() { }
|
|
72330
|
+
deletedText() {
|
|
72331
|
+
this.i18n.lang();
|
|
72332
|
+
return this.i18n.t('messageDeleted', 'Chat');
|
|
72425
72333
|
}
|
|
72426
|
-
|
|
72427
|
-
this.
|
|
72428
|
-
this.
|
|
72334
|
+
editedLabel() {
|
|
72335
|
+
this.i18n.lang();
|
|
72336
|
+
return this.i18n.t('edited', 'Chat');
|
|
72429
72337
|
}
|
|
72430
|
-
|
|
72431
|
-
|
|
72338
|
+
formatTime(date) {
|
|
72339
|
+
if (!date)
|
|
72340
|
+
return '';
|
|
72341
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
72342
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
72343
|
+
}
|
|
72344
|
+
statusIcon() {
|
|
72345
|
+
switch (this.props.status) {
|
|
72346
|
+
case 'sending':
|
|
72347
|
+
return 'time-outline';
|
|
72348
|
+
case 'failed':
|
|
72349
|
+
return 'alert-circle';
|
|
72350
|
+
case 'sent':
|
|
72351
|
+
default:
|
|
72352
|
+
return 'checkmark';
|
|
72353
|
+
}
|
|
72354
|
+
}
|
|
72355
|
+
onReactionClick(reaction) {
|
|
72356
|
+
this.reactionClick.emit({ token: reaction.token, msgId: this.props.msgId });
|
|
72357
|
+
}
|
|
72358
|
+
onReplyClick() {
|
|
72359
|
+
this.replyClick.emit({ msgId: this.props.msgId });
|
|
72360
|
+
}
|
|
72361
|
+
onDeleteClick() {
|
|
72362
|
+
this.deleteClick.emit({ msgId: this.props.msgId });
|
|
72363
|
+
}
|
|
72364
|
+
onEditClick() {
|
|
72365
|
+
this.editClick.emit({ msgId: this.props.msgId });
|
|
72432
72366
|
}
|
|
72433
72367
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72434
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MessageBubbleComponent, isStandalone: true, selector: "val-message-bubble", inputs: {
|
|
72435
|
-
<div class="
|
|
72436
|
-
@if (
|
|
72437
|
-
<
|
|
72438
|
-
@if (msg().senderAvatar) {
|
|
72439
|
-
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72440
|
-
} @else {
|
|
72441
|
-
<span>{{ initials() }}</span>
|
|
72442
|
-
}
|
|
72443
|
-
</div>
|
|
72368
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MessageBubbleComponent, isStandalone: true, selector: "val-message-bubble", inputs: { props: "props" }, outputs: { reactionClick: "reactionClick", replyClick: "replyClick", deleteClick: "deleteClick", editClick: "editClick" }, ngImport: i0, template: `
|
|
72369
|
+
<div class="message-bubble-wrapper" [class.is-mine]="props.isMine">
|
|
72370
|
+
@if (!props.isMine && !props.isDeleted) {
|
|
72371
|
+
<span class="sender-name">{{ props.senderName }}</span>
|
|
72444
72372
|
}
|
|
72445
72373
|
|
|
72446
|
-
<div class="bubble
|
|
72447
|
-
|
|
72448
|
-
class="
|
|
72449
|
-
|
|
72450
|
-
|
|
72451
|
-
|
|
72452
|
-
|
|
72453
|
-
>
|
|
72454
|
-
@if (showName() && !msg().isMine && !msg().isDeleted) {
|
|
72455
|
-
<span class="name">{{ msg().senderName }}</span>
|
|
72456
|
-
}
|
|
72374
|
+
<div class="bubble">
|
|
72375
|
+
@if (props.replyTo && !props.isDeleted) {
|
|
72376
|
+
<div class="reply-preview">
|
|
72377
|
+
<div class="reply-sender">{{ props.replyTo.senderName }}</div>
|
|
72378
|
+
<div class="reply-body">{{ props.replyTo.body }}</div>
|
|
72379
|
+
</div>
|
|
72380
|
+
}
|
|
72457
72381
|
|
|
72458
|
-
|
|
72459
|
-
|
|
72460
|
-
|
|
72461
|
-
|
|
72462
|
-
|
|
72463
|
-
}
|
|
72382
|
+
@if (props.isDeleted) {
|
|
72383
|
+
<span class="deleted-text">{{ deletedText() }}</span>
|
|
72384
|
+
} @else {
|
|
72385
|
+
<div class="bubble-body">{{ props.body }}</div>
|
|
72386
|
+
}
|
|
72464
72387
|
|
|
72465
|
-
|
|
72466
|
-
|
|
72467
|
-
|
|
72468
|
-
|
|
72469
|
-
|
|
72470
|
-
|
|
72471
|
-
|
|
72472
|
-
|
|
72473
|
-
|
|
72474
|
-
|
|
72475
|
-
|
|
72476
|
-
<div class="files">
|
|
72477
|
-
@for (att of fileAttachments(); track att.url) {
|
|
72478
|
-
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72479
|
-
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72480
|
-
<span>{{ att.name }}</span>
|
|
72481
|
-
</a>
|
|
72482
|
-
}
|
|
72483
|
-
</div>
|
|
72484
|
-
}
|
|
72485
|
-
@if (msg().body) {
|
|
72486
|
-
<span class="body">{{ msg().body }}</span>
|
|
72487
|
-
}
|
|
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>
|
|
72488
72399
|
}
|
|
72489
|
-
|
|
72490
|
-
<span class="meta">
|
|
72491
|
-
@if (msg().isEdited && !msg().isDeleted) {
|
|
72492
|
-
<span class="edited">{{ t('edited') }}</span>
|
|
72493
|
-
}
|
|
72494
|
-
<span class="time">{{ time() }}</span>
|
|
72495
|
-
@if (msg().isMine && !msg().isDeleted) {
|
|
72496
|
-
<ion-icon
|
|
72497
|
-
class="status"
|
|
72498
|
-
[class.read]="msg().status === 'read'"
|
|
72499
|
-
[class.failed]="msg().status === 'failed'"
|
|
72500
|
-
[name]="statusIcon()"
|
|
72501
|
-
aria-hidden="true"
|
|
72502
|
-
/>
|
|
72503
|
-
}
|
|
72504
|
-
</span>
|
|
72505
72400
|
</div>
|
|
72401
|
+
</div>
|
|
72506
72402
|
|
|
72507
|
-
|
|
72508
|
-
|
|
72509
|
-
|
|
72510
|
-
|
|
72511
|
-
|
|
72512
|
-
|
|
72513
|
-
|
|
72514
|
-
|
|
72515
|
-
</div>
|
|
72516
|
-
}
|
|
72517
|
-
|
|
72518
|
-
@if (!msg().isDeleted) {
|
|
72519
|
-
<div class="actions" [class.open]="actionsOpen()">
|
|
72520
|
-
<button class="act" [attr.aria-label]="t('react')" (click)="emit('react', '👍')">
|
|
72521
|
-
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72522
|
-
</button>
|
|
72523
|
-
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72524
|
-
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72403
|
+
@if (!props.isDeleted && props.reactions && props.reactions.length > 0) {
|
|
72404
|
+
<div class="reactions-bar">
|
|
72405
|
+
@for (reaction of props.reactions; track reaction.token) {
|
|
72406
|
+
<button class="reaction-btn" [class.active]="reaction.active" (click)="onReactionClick(reaction)">
|
|
72407
|
+
<span>{{ reaction.token }}</span>
|
|
72408
|
+
@if (reaction.count > 0) {
|
|
72409
|
+
<span class="reaction-count">{{ reaction.count }}</span>
|
|
72410
|
+
}
|
|
72525
72411
|
</button>
|
|
72526
|
-
@if (msg().isMine) {
|
|
72527
|
-
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72528
|
-
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72529
|
-
</button>
|
|
72530
|
-
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72531
|
-
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72532
|
-
</button>
|
|
72533
|
-
}
|
|
72534
|
-
</div>
|
|
72535
|
-
}
|
|
72536
|
-
</div>
|
|
72537
|
-
</div>
|
|
72538
|
-
`, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
72539
|
-
}
|
|
72540
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72541
|
-
type: Component,
|
|
72542
|
-
args: [{ selector: 'val-message-bubble', standalone: true, imports: [CommonModule, IonIcon], template: `
|
|
72543
|
-
<div class="row" [class.mine]="msg().isMine" [class.tail]="tail()" [class.with-avatar]="showAvatar()">
|
|
72544
|
-
@if (showAvatar() && !msg().isMine) {
|
|
72545
|
-
<div class="avatar" [class.hidden]="!tail()">
|
|
72546
|
-
@if (msg().senderAvatar) {
|
|
72547
|
-
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72548
|
-
} @else {
|
|
72549
|
-
<span>{{ initials() }}</span>
|
|
72550
72412
|
}
|
|
72551
72413
|
</div>
|
|
72552
72414
|
}
|
|
72553
72415
|
|
|
72554
|
-
|
|
72555
|
-
<div
|
|
72556
|
-
|
|
72557
|
-
|
|
72558
|
-
|
|
72559
|
-
(
|
|
72560
|
-
|
|
72561
|
-
|
|
72562
|
-
|
|
72563
|
-
<
|
|
72564
|
-
|
|
72565
|
-
|
|
72566
|
-
@if (msg().replyTo && !msg().isDeleted) {
|
|
72567
|
-
<div class="reply-preview">
|
|
72568
|
-
<span class="reply-name">{{ msg().replyTo!.senderName }}</span>
|
|
72569
|
-
<span class="reply-body">{{ msg().replyTo!.body }}</span>
|
|
72570
|
-
</div>
|
|
72571
|
-
}
|
|
72572
|
-
|
|
72573
|
-
@if (msg().isDeleted) {
|
|
72574
|
-
<span class="body deleted-text">{{ t('deleted') }}</span>
|
|
72575
|
-
} @else {
|
|
72576
|
-
@if (imageAttachments().length > 0) {
|
|
72577
|
-
<div class="attachments">
|
|
72578
|
-
@for (att of imageAttachments(); track att.url) {
|
|
72579
|
-
<img class="att-image" [src]="att.url" [alt]="att.name" />
|
|
72580
|
-
}
|
|
72581
|
-
</div>
|
|
72582
|
-
}
|
|
72583
|
-
@if (fileAttachments().length > 0) {
|
|
72584
|
-
<div class="files">
|
|
72585
|
-
@for (att of fileAttachments(); track att.url) {
|
|
72586
|
-
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72587
|
-
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72588
|
-
<span>{{ att.name }}</span>
|
|
72589
|
-
</a>
|
|
72590
|
-
}
|
|
72591
|
-
</div>
|
|
72592
|
-
}
|
|
72593
|
-
@if (msg().body) {
|
|
72594
|
-
<span class="body">{{ msg().body }}</span>
|
|
72595
|
-
}
|
|
72416
|
+
@if (!props.isDeleted) {
|
|
72417
|
+
<div class="bubble-actions">
|
|
72418
|
+
<ion-button fill="clear" size="small" (click)="onReplyClick()">
|
|
72419
|
+
<ion-icon name="return-down-back-outline" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
72420
|
+
</ion-button>
|
|
72421
|
+
@if (props.isMine) {
|
|
72422
|
+
<ion-button fill="clear" size="small" (click)="onEditClick()">
|
|
72423
|
+
<ion-icon name="pencil" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
72424
|
+
</ion-button>
|
|
72425
|
+
<ion-button fill="clear" size="small" color="danger" (click)="onDeleteClick()">
|
|
72426
|
+
<ion-icon name="trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
72427
|
+
</ion-button>
|
|
72596
72428
|
}
|
|
72597
|
-
|
|
72598
|
-
<span class="meta">
|
|
72599
|
-
@if (msg().isEdited && !msg().isDeleted) {
|
|
72600
|
-
<span class="edited">{{ t('edited') }}</span>
|
|
72601
|
-
}
|
|
72602
|
-
<span class="time">{{ time() }}</span>
|
|
72603
|
-
@if (msg().isMine && !msg().isDeleted) {
|
|
72604
|
-
<ion-icon
|
|
72605
|
-
class="status"
|
|
72606
|
-
[class.read]="msg().status === 'read'"
|
|
72607
|
-
[class.failed]="msg().status === 'failed'"
|
|
72608
|
-
[name]="statusIcon()"
|
|
72609
|
-
aria-hidden="true"
|
|
72610
|
-
/>
|
|
72611
|
-
}
|
|
72612
|
-
</span>
|
|
72613
72429
|
</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
|
+
}
|
|
72614
72441
|
|
|
72615
|
-
|
|
72616
|
-
|
|
72617
|
-
|
|
72618
|
-
|
|
72619
|
-
|
|
72620
|
-
<span class="count">{{ r.count }}</span>
|
|
72621
|
-
</button>
|
|
72622
|
-
}
|
|
72442
|
+
<div class="bubble">
|
|
72443
|
+
@if (props.replyTo && !props.isDeleted) {
|
|
72444
|
+
<div class="reply-preview">
|
|
72445
|
+
<div class="reply-sender">{{ props.replyTo.senderName }}</div>
|
|
72446
|
+
<div class="reply-body">{{ props.replyTo.body }}</div>
|
|
72623
72447
|
</div>
|
|
72624
72448
|
}
|
|
72625
72449
|
|
|
72626
|
-
@if (
|
|
72627
|
-
<
|
|
72628
|
-
|
|
72629
|
-
|
|
72630
|
-
</button>
|
|
72631
|
-
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72632
|
-
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72633
|
-
</button>
|
|
72634
|
-
@if (msg().isMine) {
|
|
72635
|
-
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72636
|
-
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72637
|
-
</button>
|
|
72638
|
-
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72639
|
-
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72640
|
-
</button>
|
|
72641
|
-
}
|
|
72642
|
-
</div>
|
|
72450
|
+
@if (props.isDeleted) {
|
|
72451
|
+
<span class="deleted-text">{{ deletedText() }}</span>
|
|
72452
|
+
} @else {
|
|
72453
|
+
<div class="bubble-body">{{ props.body }}</div>
|
|
72643
72454
|
}
|
|
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>
|
|
72644
72469
|
</div>
|
|
72470
|
+
|
|
72471
|
+
@if (!props.isDeleted && props.reactions && props.reactions.length > 0) {
|
|
72472
|
+
<div class="reactions-bar">
|
|
72473
|
+
@for (reaction of props.reactions; track reaction.token) {
|
|
72474
|
+
<button class="reaction-btn" [class.active]="reaction.active" (click)="onReactionClick(reaction)">
|
|
72475
|
+
<span>{{ reaction.token }}</span>
|
|
72476
|
+
@if (reaction.count > 0) {
|
|
72477
|
+
<span class="reaction-count">{{ reaction.count }}</span>
|
|
72478
|
+
}
|
|
72479
|
+
</button>
|
|
72480
|
+
}
|
|
72481
|
+
</div>
|
|
72482
|
+
}
|
|
72483
|
+
|
|
72484
|
+
@if (!props.isDeleted) {
|
|
72485
|
+
<div class="bubble-actions">
|
|
72486
|
+
<ion-button fill="clear" size="small" (click)="onReplyClick()">
|
|
72487
|
+
<ion-icon name="return-down-back-outline" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
72488
|
+
</ion-button>
|
|
72489
|
+
@if (props.isMine) {
|
|
72490
|
+
<ion-button fill="clear" size="small" (click)="onEditClick()">
|
|
72491
|
+
<ion-icon name="pencil" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
72492
|
+
</ion-button>
|
|
72493
|
+
<ion-button fill="clear" size="small" color="danger" (click)="onDeleteClick()">
|
|
72494
|
+
<ion-icon name="trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
72495
|
+
</ion-button>
|
|
72496
|
+
}
|
|
72497
|
+
</div>
|
|
72498
|
+
}
|
|
72645
72499
|
</div>
|
|
72646
|
-
`, styles: ["
|
|
72647
|
-
}], ctorParameters: () => []
|
|
72500
|
+
`, 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"] }]
|
|
72501
|
+
}], ctorParameters: () => [], propDecorators: { props: [{
|
|
72502
|
+
type: Input
|
|
72503
|
+
}], reactionClick: [{
|
|
72504
|
+
type: Output
|
|
72505
|
+
}], replyClick: [{
|
|
72506
|
+
type: Output
|
|
72507
|
+
}], deleteClick: [{
|
|
72508
|
+
type: Output
|
|
72509
|
+
}], editClick: [{
|
|
72510
|
+
type: Output
|
|
72511
|
+
}] } });
|
|
72648
72512
|
|
|
72649
72513
|
addIcons({ sendOutline });
|
|
72650
72514
|
const CHAT_INPUT_I18N = {
|
|
@@ -72737,6 +72601,10 @@ class ChatInputComponent {
|
|
|
72737
72601
|
[autoGrow]="true"
|
|
72738
72602
|
[rows]="1"
|
|
72739
72603
|
[value]="body()"
|
|
72604
|
+
enterkeyhint="send"
|
|
72605
|
+
autocomplete="off"
|
|
72606
|
+
autocorrect="off"
|
|
72607
|
+
spellcheck="false"
|
|
72740
72608
|
(ionInput)="onInput($event)"
|
|
72741
72609
|
(keydown)="onKeydown($event)"
|
|
72742
72610
|
></ion-textarea>
|
|
@@ -72769,6 +72637,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72769
72637
|
[autoGrow]="true"
|
|
72770
72638
|
[rows]="1"
|
|
72771
72639
|
[value]="body()"
|
|
72640
|
+
enterkeyhint="send"
|
|
72641
|
+
autocomplete="off"
|
|
72642
|
+
autocorrect="off"
|
|
72643
|
+
spellcheck="false"
|
|
72772
72644
|
(ionInput)="onInput($event)"
|
|
72773
72645
|
(keydown)="onKeydown($event)"
|
|
72774
72646
|
></ion-textarea>
|
|
@@ -72796,340 +72668,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72796
72668
|
type: Output
|
|
72797
72669
|
}] } });
|
|
72798
72670
|
|
|
72799
|
-
addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline });
|
|
72800
|
-
const CHAT_COMPOSER_I18N = {
|
|
72801
|
-
es: {
|
|
72802
|
-
placeholder: 'Escribe un mensaje',
|
|
72803
|
-
send: 'Enviar',
|
|
72804
|
-
attach: 'Adjuntar',
|
|
72805
|
-
emoji: 'Emoji',
|
|
72806
|
-
voice: 'Mensaje de voz',
|
|
72807
|
-
replyingTo: 'Respondiendo a',
|
|
72808
|
-
cancel: 'Cancelar',
|
|
72809
|
-
},
|
|
72810
|
-
en: {
|
|
72811
|
-
placeholder: 'Write a message',
|
|
72812
|
-
send: 'Send',
|
|
72813
|
-
attach: 'Attach',
|
|
72814
|
-
emoji: 'Emoji',
|
|
72815
|
-
voice: 'Voice message',
|
|
72816
|
-
replyingTo: 'Replying to',
|
|
72817
|
-
cancel: 'Cancel',
|
|
72818
|
-
},
|
|
72819
|
-
};
|
|
72820
|
-
const QUICK_EMOJIS = ['👍', '❤️', '😂', '🎉', '🙏', '🔥', '😮', '😢'];
|
|
72821
|
-
/**
|
|
72822
|
-
* val-chat-composer — barra de redacción de chat moderna (signal inputs).
|
|
72823
|
-
*
|
|
72824
|
-
* Pill con: adjuntar (+), textarea auto-grow, emoji (fila rápida) y mic/enviar
|
|
72825
|
-
* (mic cuando está vacío, enviar cuando hay texto — patrón WhatsApp/Telegram).
|
|
72826
|
-
* Muestra una barra de contexto "respondiendo a" cuando `replyingTo` está seteado.
|
|
72827
|
-
* `typing` se emite con debounce (máx. 1 vez cada 2s) para no spamear writes.
|
|
72828
|
-
*/
|
|
72829
|
-
class ChatComposerComponent {
|
|
72830
|
-
constructor() {
|
|
72831
|
-
this.i18n = inject(I18nService);
|
|
72832
|
-
this.placeholder = input('');
|
|
72833
|
-
this.disabled = input(false);
|
|
72834
|
-
this.maxLength = input(2000);
|
|
72835
|
-
this.replyingTo = input(null);
|
|
72836
|
-
this.showAttach = input(true);
|
|
72837
|
-
this.showEmoji = input(true);
|
|
72838
|
-
this.showMic = input(true);
|
|
72839
|
-
this.send = output();
|
|
72840
|
-
this.typing = output();
|
|
72841
|
-
this.attach = output();
|
|
72842
|
-
this.voice = output();
|
|
72843
|
-
this.cancelReply = output();
|
|
72844
|
-
this.body = signal('');
|
|
72845
|
-
this.emojiOpen = signal(false);
|
|
72846
|
-
this.quickEmojis = QUICK_EMOJIS;
|
|
72847
|
-
this.lastTypingEmit = 0;
|
|
72848
|
-
this.canSend = computed(() => {
|
|
72849
|
-
const t = this.body().trim();
|
|
72850
|
-
return t.length > 0 && t.length <= this.maxLength() && !this.disabled();
|
|
72851
|
-
});
|
|
72852
|
-
this.placeholderText = computed(() => {
|
|
72853
|
-
this.i18n.lang();
|
|
72854
|
-
return this.placeholder() || this.i18n.t('placeholder', 'ChatComposer');
|
|
72855
|
-
});
|
|
72856
|
-
this.i18n.registerDefaults('ChatComposer', CHAT_COMPOSER_I18N);
|
|
72857
|
-
}
|
|
72858
|
-
onInput(event) {
|
|
72859
|
-
const target = event.target;
|
|
72860
|
-
this.body.set(target.value ?? '');
|
|
72861
|
-
this.emitTypingDebounced();
|
|
72862
|
-
}
|
|
72863
|
-
onKeydown(event) {
|
|
72864
|
-
if (event.key === 'Enter' && !event.shiftKey) {
|
|
72865
|
-
event.preventDefault();
|
|
72866
|
-
this.onSend();
|
|
72867
|
-
}
|
|
72868
|
-
}
|
|
72869
|
-
onSend() {
|
|
72870
|
-
const trimmed = this.body().trim();
|
|
72871
|
-
if (!trimmed || trimmed.length > this.maxLength())
|
|
72872
|
-
return;
|
|
72873
|
-
this.send.emit(trimmed);
|
|
72874
|
-
this.body.set('');
|
|
72875
|
-
this.emojiOpen.set(false);
|
|
72876
|
-
}
|
|
72877
|
-
toggleEmoji() {
|
|
72878
|
-
this.emojiOpen.update(v => !v);
|
|
72879
|
-
}
|
|
72880
|
-
insertEmoji(emoji) {
|
|
72881
|
-
this.body.update(v => v + emoji);
|
|
72882
|
-
}
|
|
72883
|
-
onFile(event) {
|
|
72884
|
-
const input = event.target;
|
|
72885
|
-
const file = input.files?.[0];
|
|
72886
|
-
if (file)
|
|
72887
|
-
this.attach.emit(file);
|
|
72888
|
-
input.value = '';
|
|
72889
|
-
}
|
|
72890
|
-
/**
|
|
72891
|
-
* Emite typing como máximo 1 vez cada 2s. No usa Date.now() directo en plantillas
|
|
72892
|
-
* de workflow; aquí estamos en runtime de navegador, performance.now es seguro.
|
|
72893
|
-
*/
|
|
72894
|
-
emitTypingDebounced() {
|
|
72895
|
-
const now = performance.now();
|
|
72896
|
-
if (now - this.lastTypingEmit > 2000) {
|
|
72897
|
-
this.lastTypingEmit = now;
|
|
72898
|
-
this.typing.emit();
|
|
72899
|
-
}
|
|
72900
|
-
}
|
|
72901
|
-
t(key) {
|
|
72902
|
-
return this.i18n.t(key, 'ChatComposer');
|
|
72903
|
-
}
|
|
72904
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72905
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatComposerComponent, isStandalone: true, selector: "val-chat-composer", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, replyingTo: { classPropertyName: "replyingTo", publicName: "replyingTo", isSignal: true, isRequired: false, transformFunction: null }, showAttach: { classPropertyName: "showAttach", publicName: "showAttach", isSignal: true, isRequired: false, transformFunction: null }, showEmoji: { classPropertyName: "showEmoji", publicName: "showEmoji", isSignal: true, isRequired: false, transformFunction: null }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", attach: "attach", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
|
|
72906
|
-
<div class="composer">
|
|
72907
|
-
@if (replyingTo()) {
|
|
72908
|
-
<div class="reply-bar">
|
|
72909
|
-
<div class="reply-content">
|
|
72910
|
-
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
72911
|
-
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
72912
|
-
</div>
|
|
72913
|
-
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
72914
|
-
<ion-icon name="close-outline" aria-hidden="true" />
|
|
72915
|
-
</button>
|
|
72916
|
-
</div>
|
|
72917
|
-
}
|
|
72918
|
-
|
|
72919
|
-
@if (emojiOpen()) {
|
|
72920
|
-
<div class="emoji-row">
|
|
72921
|
-
@for (e of quickEmojis; track e) {
|
|
72922
|
-
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
72923
|
-
}
|
|
72924
|
-
</div>
|
|
72925
|
-
}
|
|
72926
|
-
|
|
72927
|
-
<div class="pill">
|
|
72928
|
-
@if (showAttach()) {
|
|
72929
|
-
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
72930
|
-
<ion-icon name="add-outline" aria-hidden="true" />
|
|
72931
|
-
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
72932
|
-
</label>
|
|
72933
|
-
}
|
|
72934
|
-
|
|
72935
|
-
<ion-textarea
|
|
72936
|
-
class="field"
|
|
72937
|
-
[placeholder]="placeholderText()"
|
|
72938
|
-
[disabled]="disabled()"
|
|
72939
|
-
[maxlength]="maxLength()"
|
|
72940
|
-
[autoGrow]="true"
|
|
72941
|
-
[rows]="1"
|
|
72942
|
-
[value]="body()"
|
|
72943
|
-
(ionInput)="onInput($event)"
|
|
72944
|
-
(keydown)="onKeydown($event)"
|
|
72945
|
-
></ion-textarea>
|
|
72946
|
-
|
|
72947
|
-
@if (showEmoji()) {
|
|
72948
|
-
<button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
|
|
72949
|
-
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72950
|
-
</button>
|
|
72951
|
-
}
|
|
72952
|
-
|
|
72953
|
-
@if (canSend()) {
|
|
72954
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
72955
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
72956
|
-
</button>
|
|
72957
|
-
} @else if (showMic()) {
|
|
72958
|
-
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
|
|
72959
|
-
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
72960
|
-
</button>
|
|
72961
|
-
}
|
|
72962
|
-
</div>
|
|
72963
|
-
</div>
|
|
72964
|
-
`, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
72965
|
-
}
|
|
72966
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
|
|
72967
|
-
type: Component,
|
|
72968
|
-
args: [{ selector: 'val-chat-composer', standalone: true, imports: [CommonModule, IonTextarea, IonIcon], template: `
|
|
72969
|
-
<div class="composer">
|
|
72970
|
-
@if (replyingTo()) {
|
|
72971
|
-
<div class="reply-bar">
|
|
72972
|
-
<div class="reply-content">
|
|
72973
|
-
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
72974
|
-
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
72975
|
-
</div>
|
|
72976
|
-
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
72977
|
-
<ion-icon name="close-outline" aria-hidden="true" />
|
|
72978
|
-
</button>
|
|
72979
|
-
</div>
|
|
72980
|
-
}
|
|
72981
|
-
|
|
72982
|
-
@if (emojiOpen()) {
|
|
72983
|
-
<div class="emoji-row">
|
|
72984
|
-
@for (e of quickEmojis; track e) {
|
|
72985
|
-
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
72986
|
-
}
|
|
72987
|
-
</div>
|
|
72988
|
-
}
|
|
72989
|
-
|
|
72990
|
-
<div class="pill">
|
|
72991
|
-
@if (showAttach()) {
|
|
72992
|
-
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
72993
|
-
<ion-icon name="add-outline" aria-hidden="true" />
|
|
72994
|
-
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
72995
|
-
</label>
|
|
72996
|
-
}
|
|
72997
|
-
|
|
72998
|
-
<ion-textarea
|
|
72999
|
-
class="field"
|
|
73000
|
-
[placeholder]="placeholderText()"
|
|
73001
|
-
[disabled]="disabled()"
|
|
73002
|
-
[maxlength]="maxLength()"
|
|
73003
|
-
[autoGrow]="true"
|
|
73004
|
-
[rows]="1"
|
|
73005
|
-
[value]="body()"
|
|
73006
|
-
(ionInput)="onInput($event)"
|
|
73007
|
-
(keydown)="onKeydown($event)"
|
|
73008
|
-
></ion-textarea>
|
|
73009
|
-
|
|
73010
|
-
@if (showEmoji()) {
|
|
73011
|
-
<button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
|
|
73012
|
-
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
73013
|
-
</button>
|
|
73014
|
-
}
|
|
73015
|
-
|
|
73016
|
-
@if (canSend()) {
|
|
73017
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73018
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73019
|
-
</button>
|
|
73020
|
-
} @else if (showMic()) {
|
|
73021
|
-
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
|
|
73022
|
-
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73023
|
-
</button>
|
|
73024
|
-
}
|
|
73025
|
-
</div>
|
|
73026
|
-
</div>
|
|
73027
|
-
`, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"] }]
|
|
73028
|
-
}], ctorParameters: () => [] });
|
|
73029
|
-
|
|
73030
|
-
/**
|
|
73031
|
-
* val-conversation-list-item — fila de lista de conversaciones estilo
|
|
73032
|
-
* Telegram/WhatsApp: avatar (imagen o iniciales), nombre + hora relativa,
|
|
73033
|
-
* preview del último mensaje + badge de no-leídos. Signal inputs, dark-safe.
|
|
73034
|
-
*
|
|
73035
|
-
* @example
|
|
73036
|
-
* <val-conversation-list-item
|
|
73037
|
-
* [name]="conv.name"
|
|
73038
|
-
* [lastMessage]="conv.lastMsgPreview"
|
|
73039
|
-
* [timestamp]="conv.lastMsgAt"
|
|
73040
|
-
* [unreadCount]="conv.unread"
|
|
73041
|
-
* (select)="open(conv.convId)" />
|
|
73042
|
-
*/
|
|
73043
|
-
class ConversationListItemComponent {
|
|
73044
|
-
constructor() {
|
|
73045
|
-
this.i18n = inject(I18nService);
|
|
73046
|
-
this.name = input('');
|
|
73047
|
-
this.avatarUrl = input(undefined);
|
|
73048
|
-
this.lastMessage = input('');
|
|
73049
|
-
this.timestamp = input(undefined);
|
|
73050
|
-
this.unreadCount = input(0);
|
|
73051
|
-
this.locale = input('es-CL');
|
|
73052
|
-
this.select = output();
|
|
73053
|
-
this.initials = computed(() => {
|
|
73054
|
-
const name = this.name().trim();
|
|
73055
|
-
if (!name)
|
|
73056
|
-
return '?';
|
|
73057
|
-
const parts = name.split(/\s+/);
|
|
73058
|
-
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
73059
|
-
});
|
|
73060
|
-
this.timeLabel = computed(() => formatRelativeTime(this.timestamp(), this.locale(), {
|
|
73061
|
-
today: '',
|
|
73062
|
-
yesterday: this.i18n.t('yesterday', '_global'),
|
|
73063
|
-
}));
|
|
73064
|
-
this.i18n.registerDefaults('ConversationListItem', {
|
|
73065
|
-
es: { empty: 'Sin mensajes' },
|
|
73066
|
-
en: { empty: 'No messages' },
|
|
73067
|
-
});
|
|
73068
|
-
}
|
|
73069
|
-
emptyText() {
|
|
73070
|
-
this.i18n.lang();
|
|
73071
|
-
return this.i18n.t('empty', 'ConversationListItem');
|
|
73072
|
-
}
|
|
73073
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73074
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ConversationListItemComponent, isStandalone: true, selector: "val-conversation-list-item", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, avatarUrl: { classPropertyName: "avatarUrl", publicName: "avatarUrl", isSignal: true, isRequired: false, transformFunction: null }, lastMessage: { classPropertyName: "lastMessage", publicName: "lastMessage", isSignal: true, isRequired: false, transformFunction: null }, timestamp: { classPropertyName: "timestamp", publicName: "timestamp", isSignal: true, isRequired: false, transformFunction: null }, unreadCount: { classPropertyName: "unreadCount", publicName: "unreadCount", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select" }, ngImport: i0, template: `
|
|
73075
|
-
<button class="item" (click)="select.emit()">
|
|
73076
|
-
<div class="avatar">
|
|
73077
|
-
@if (avatarUrl()) {
|
|
73078
|
-
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73079
|
-
} @else {
|
|
73080
|
-
<span>{{ initials() }}</span>
|
|
73081
|
-
}
|
|
73082
|
-
</div>
|
|
73083
|
-
<div class="content">
|
|
73084
|
-
<div class="top">
|
|
73085
|
-
<span class="name">{{ name() }}</span>
|
|
73086
|
-
@if (timeLabel()) {
|
|
73087
|
-
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73088
|
-
}
|
|
73089
|
-
</div>
|
|
73090
|
-
<div class="bottom">
|
|
73091
|
-
<span class="preview" [class.muted]="!lastMessage()">
|
|
73092
|
-
{{ lastMessage() || emptyText() }}
|
|
73093
|
-
</span>
|
|
73094
|
-
@if (unreadCount() > 0) {
|
|
73095
|
-
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73096
|
-
}
|
|
73097
|
-
</div>
|
|
73098
|
-
</div>
|
|
73099
|
-
</button>
|
|
73100
|
-
`, isInline: true, styles: [":host{display:block}.item{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;border:none;background:transparent;cursor:pointer;text-align:left;border-radius:12px;transition:background .12s ease}.item:hover{background:var(--ion-color-step-50, rgba(127, 127, 127, .06))}.avatar{width:48px;height:48px;flex-shrink:0;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-weight:700;font-size:1rem}.avatar img{width:100%;height:100%;object-fit:cover}.content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.top,.bottom{display:flex;align-items:center;gap:8px}.name{flex:1;min-width:0;font-weight:600;font-size:.9375rem;color:var(--ion-text-color, #000);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.time{flex-shrink:0;font-size:.75rem;color:var(--ion-color-medium, #92949c)}.time.unread{color:var(--ion-color-primary);font-weight:600}.preview{flex:1;min-width:0;font-size:.875rem;color:var(--ion-color-medium, #92949c);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview.muted{font-style:italic;opacity:.7}.badge{flex-shrink:0;min-width:20px;height:20px;padding:0 6px;border-radius:10px;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);font-size:.6875rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
|
|
73101
|
-
}
|
|
73102
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, decorators: [{
|
|
73103
|
-
type: Component,
|
|
73104
|
-
args: [{ selector: 'val-conversation-list-item', standalone: true, imports: [CommonModule], template: `
|
|
73105
|
-
<button class="item" (click)="select.emit()">
|
|
73106
|
-
<div class="avatar">
|
|
73107
|
-
@if (avatarUrl()) {
|
|
73108
|
-
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73109
|
-
} @else {
|
|
73110
|
-
<span>{{ initials() }}</span>
|
|
73111
|
-
}
|
|
73112
|
-
</div>
|
|
73113
|
-
<div class="content">
|
|
73114
|
-
<div class="top">
|
|
73115
|
-
<span class="name">{{ name() }}</span>
|
|
73116
|
-
@if (timeLabel()) {
|
|
73117
|
-
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73118
|
-
}
|
|
73119
|
-
</div>
|
|
73120
|
-
<div class="bottom">
|
|
73121
|
-
<span class="preview" [class.muted]="!lastMessage()">
|
|
73122
|
-
{{ lastMessage() || emptyText() }}
|
|
73123
|
-
</span>
|
|
73124
|
-
@if (unreadCount() > 0) {
|
|
73125
|
-
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73126
|
-
}
|
|
73127
|
-
</div>
|
|
73128
|
-
</div>
|
|
73129
|
-
</button>
|
|
73130
|
-
`, styles: [":host{display:block}.item{display:flex;align-items:center;gap:12px;width:100%;padding:10px 12px;border:none;background:transparent;cursor:pointer;text-align:left;border-radius:12px;transition:background .12s ease}.item:hover{background:var(--ion-color-step-50, rgba(127, 127, 127, .06))}.avatar{width:48px;height:48px;flex-shrink:0;border-radius:50%;overflow:hidden;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-weight:700;font-size:1rem}.avatar img{width:100%;height:100%;object-fit:cover}.content{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.top,.bottom{display:flex;align-items:center;gap:8px}.name{flex:1;min-width:0;font-weight:600;font-size:.9375rem;color:var(--ion-text-color, #000);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.time{flex-shrink:0;font-size:.75rem;color:var(--ion-color-medium, #92949c)}.time.unread{color:var(--ion-color-primary);font-weight:600}.preview{flex:1;min-width:0;font-size:.875rem;color:var(--ion-color-medium, #92949c);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview.muted{font-style:italic;opacity:.7}.badge{flex-shrink:0;min-width:20px;height:20px;padding:0 6px;border-radius:10px;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);font-size:.6875rem;font-weight:700;display:inline-flex;align-items:center;justify-content:center}\n"] }]
|
|
73131
|
-
}], ctorParameters: () => [] });
|
|
73132
|
-
|
|
73133
72671
|
const TYPING_I18N = {
|
|
73134
72672
|
es: {
|
|
73135
72673
|
one: '{name} esta escribiendo',
|
|
@@ -73145,9 +72683,8 @@ const TYPING_I18N = {
|
|
|
73145
72683
|
/**
|
|
73146
72684
|
* val-typing-indicator
|
|
73147
72685
|
*
|
|
73148
|
-
* Muestra quienes estan escribiendo con animacion de tres puntos.
|
|
73149
|
-
* si
|
|
73150
|
-
* recompute al cambiar la lista.
|
|
72686
|
+
* Muestra quienes estan escribiendo en el chat con animacion de tres puntos.
|
|
72687
|
+
* Se oculta automaticamente si typingUsers esta vacio.
|
|
73151
72688
|
*
|
|
73152
72689
|
* @example
|
|
73153
72690
|
* <val-typing-indicator [typingUsers]="['Maria', 'Carlos']" />
|
|
@@ -73155,10 +72692,10 @@ const TYPING_I18N = {
|
|
|
73155
72692
|
class TypingIndicatorComponent {
|
|
73156
72693
|
constructor() {
|
|
73157
72694
|
this.i18n = inject(I18nService);
|
|
73158
|
-
this.typingUsers =
|
|
72695
|
+
this.typingUsers = [];
|
|
73159
72696
|
this.typingText = computed(() => {
|
|
73160
72697
|
this.i18n.lang();
|
|
73161
|
-
const users = this.typingUsers
|
|
72698
|
+
const users = this.typingUsers ?? [];
|
|
73162
72699
|
if (users.length === 0)
|
|
73163
72700
|
return '';
|
|
73164
72701
|
if (users.length === 1) {
|
|
@@ -73172,8 +72709,8 @@ class TypingIndicatorComponent {
|
|
|
73172
72709
|
this.i18n.registerDefaults('TypingIndicator', TYPING_I18N);
|
|
73173
72710
|
}
|
|
73174
72711
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73175
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: TypingIndicatorComponent, isStandalone: true, selector: "val-typing-indicator", inputs: { typingUsers:
|
|
73176
|
-
@if (typingUsers
|
|
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 && typingUsers.length > 0) {
|
|
73177
72714
|
<div class="typing-indicator">
|
|
73178
72715
|
<div class="dots">
|
|
73179
72716
|
<span></span>
|
|
@@ -73188,7 +72725,7 @@ class TypingIndicatorComponent {
|
|
|
73188
72725
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, decorators: [{
|
|
73189
72726
|
type: Component,
|
|
73190
72727
|
args: [{ selector: 'val-typing-indicator', standalone: true, imports: [CommonModule], template: `
|
|
73191
|
-
@if (typingUsers
|
|
72728
|
+
@if (typingUsers && typingUsers.length > 0) {
|
|
73192
72729
|
<div class="typing-indicator">
|
|
73193
72730
|
<div class="dots">
|
|
73194
72731
|
<span></span>
|
|
@@ -73199,280 +72736,270 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73199
72736
|
</div>
|
|
73200
72737
|
}
|
|
73201
72738
|
`, 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"] }]
|
|
73202
|
-
}], ctorParameters: () => []
|
|
72739
|
+
}], ctorParameters: () => [], propDecorators: { typingUsers: [{
|
|
72740
|
+
type: Input
|
|
72741
|
+
}] } });
|
|
73203
72742
|
|
|
73204
|
-
addIcons({
|
|
72743
|
+
addIcons({ chatbubblesOutline });
|
|
73205
72744
|
const CHAT_WINDOW_I18N = {
|
|
73206
72745
|
es: {
|
|
73207
72746
|
conversationClosed: 'Esta conversacion esta cerrada',
|
|
73208
72747
|
loading: 'Cargando mensajes...',
|
|
73209
|
-
noMessages: '
|
|
73210
|
-
newMessages: 'Mensajes nuevos',
|
|
72748
|
+
noMessages: 'No hay mensajes aun',
|
|
73211
72749
|
},
|
|
73212
72750
|
en: {
|
|
73213
72751
|
conversationClosed: 'This conversation is closed',
|
|
73214
72752
|
loading: 'Loading messages...',
|
|
73215
|
-
noMessages: 'No messages yet
|
|
73216
|
-
newMessages: 'New messages',
|
|
72753
|
+
noMessages: 'No messages yet',
|
|
73217
72754
|
},
|
|
73218
72755
|
};
|
|
73219
72756
|
/**
|
|
73220
|
-
* val-chat-window
|
|
72757
|
+
* val-chat-window
|
|
73221
72758
|
*
|
|
73222
|
-
*
|
|
73223
|
-
*
|
|
73224
|
-
*
|
|
73225
|
-
*
|
|
72759
|
+
* Ventana de chat completa. Muestra la lista de mensajes agrupados por fecha,
|
|
72760
|
+
* el indicador de escritura, el input de mensaje (solo si isOpen) y un banner
|
|
72761
|
+
* cuando la conversacion esta cerrada. Auto-scroll al ultimo mensaje cuando
|
|
72762
|
+
* llegan mensajes nuevos.
|
|
73226
72763
|
*
|
|
73227
|
-
*
|
|
73228
|
-
*
|
|
73229
|
-
*
|
|
72764
|
+
* @example
|
|
72765
|
+
* <val-chat-window
|
|
72766
|
+
* [convId]="conv.id"
|
|
72767
|
+
* [messages]="messages()"
|
|
72768
|
+
* [currentUserId]="userId"
|
|
72769
|
+
* [isOpen]="conv.status === 'open'"
|
|
72770
|
+
* [typingUsers]="typingUsers()"
|
|
72771
|
+
* (sendMessage)="onSend($event)"
|
|
72772
|
+
* (reactionClick)="onReaction($event)"
|
|
72773
|
+
* />
|
|
73230
72774
|
*/
|
|
73231
72775
|
class ChatWindowComponent {
|
|
73232
72776
|
constructor() {
|
|
73233
72777
|
this.i18n = inject(I18nService);
|
|
73234
|
-
this.convId =
|
|
73235
|
-
this.messages =
|
|
73236
|
-
this.
|
|
73237
|
-
this.
|
|
73238
|
-
this.
|
|
73239
|
-
this.
|
|
73240
|
-
this.
|
|
73241
|
-
this.
|
|
73242
|
-
this.
|
|
73243
|
-
this.
|
|
73244
|
-
this.
|
|
73245
|
-
this.
|
|
73246
|
-
this.
|
|
73247
|
-
this.
|
|
73248
|
-
this.
|
|
73249
|
-
|
|
73250
|
-
this.replyingTo = signal(null);
|
|
73251
|
-
this.atBottom = signal(true);
|
|
73252
|
-
this.hasNew = signal(false);
|
|
73253
|
-
this.locale = computed(() => (this.i18n.lang() === 'en' ? 'en-US' : 'es-CL'));
|
|
73254
|
-
this.rows = computed(() => {
|
|
73255
|
-
const msgs = this.messages();
|
|
72778
|
+
this.convId = '';
|
|
72779
|
+
this.messages = [];
|
|
72780
|
+
this.participants = [];
|
|
72781
|
+
this.currentUserId = '';
|
|
72782
|
+
this.isOpen = true;
|
|
72783
|
+
this.typingUsers = [];
|
|
72784
|
+
this.isLoading = false;
|
|
72785
|
+
this.sendMessage = new EventEmitter();
|
|
72786
|
+
this.loadMore = new EventEmitter();
|
|
72787
|
+
this.reactionClick = new EventEmitter();
|
|
72788
|
+
this.deleteMessage = new EventEmitter();
|
|
72789
|
+
this.replyTo = new EventEmitter();
|
|
72790
|
+
this.prevMessageCount = 0;
|
|
72791
|
+
this.shouldScrollToBottom = false;
|
|
72792
|
+
this.messageGroups = computed(() => {
|
|
72793
|
+
const msgs = this.messages ?? [];
|
|
73256
72794
|
if (msgs.length === 0)
|
|
73257
72795
|
return [];
|
|
73258
|
-
const
|
|
73259
|
-
|
|
73260
|
-
|
|
73261
|
-
|
|
73262
|
-
|
|
73263
|
-
|
|
73264
|
-
|
|
73265
|
-
|
|
73266
|
-
|
|
73267
|
-
|
|
73268
|
-
|
|
73269
|
-
|
|
73270
|
-
|
|
73271
|
-
|
|
72796
|
+
const groups = [];
|
|
72797
|
+
let currentLabel = '';
|
|
72798
|
+
let currentGroup = [];
|
|
72799
|
+
for (const msg of msgs) {
|
|
72800
|
+
const label = this.formatDateLabel(msg.createdAt);
|
|
72801
|
+
if (label !== currentLabel) {
|
|
72802
|
+
if (currentGroup.length > 0) {
|
|
72803
|
+
groups.push({ dateLabel: currentLabel, messages: currentGroup });
|
|
72804
|
+
}
|
|
72805
|
+
currentLabel = label;
|
|
72806
|
+
currentGroup = [msg];
|
|
72807
|
+
}
|
|
72808
|
+
else {
|
|
72809
|
+
currentGroup.push(msg);
|
|
73272
72810
|
}
|
|
73273
|
-
const prev = msgs[i - 1];
|
|
73274
|
-
const next = msgs[i + 1];
|
|
73275
|
-
const firstInGroup = newDay || !prev || prev.senderId !== m.senderId;
|
|
73276
|
-
const nextDay = next
|
|
73277
|
-
? `${new Date(next.createdAt).getFullYear()}-${new Date(next.createdAt).getMonth()}-${new Date(next.createdAt).getDate()}`
|
|
73278
|
-
: '';
|
|
73279
|
-
const lastInGroup = !next || next.senderId !== m.senderId || nextDay !== day;
|
|
73280
|
-
out.push({
|
|
73281
|
-
kind: 'msg',
|
|
73282
|
-
id: m.msgId,
|
|
73283
|
-
msg: m,
|
|
73284
|
-
showName: firstInGroup && !m.isMine,
|
|
73285
|
-
showAvatar: lastInGroup && !m.isMine,
|
|
73286
|
-
tail: lastInGroup,
|
|
73287
|
-
});
|
|
73288
72811
|
}
|
|
73289
|
-
|
|
72812
|
+
if (currentGroup.length > 0) {
|
|
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');
|
|
73290
72824
|
});
|
|
73291
72825
|
this.i18n.registerDefaults('ChatWindow', CHAT_WINDOW_I18N);
|
|
73292
|
-
|
|
73293
|
-
|
|
73294
|
-
|
|
73295
|
-
|
|
73296
|
-
|
|
73297
|
-
|
|
73298
|
-
|
|
73299
|
-
|
|
73300
|
-
|
|
73301
|
-
|
|
73302
|
-
|
|
73303
|
-
|
|
73304
|
-
|
|
73305
|
-
|
|
73306
|
-
|
|
73307
|
-
|
|
73308
|
-
|
|
72826
|
+
}
|
|
72827
|
+
ngAfterViewInit() {
|
|
72828
|
+
if (typeof window !== 'undefined' && window.visualViewport) {
|
|
72829
|
+
this.vvListener = () => this.adjustToKeyboard();
|
|
72830
|
+
window.visualViewport.addEventListener('resize', this.vvListener);
|
|
72831
|
+
this.adjustToKeyboard();
|
|
72832
|
+
}
|
|
72833
|
+
}
|
|
72834
|
+
ngOnDestroy() {
|
|
72835
|
+
if (this.vvListener && typeof window !== 'undefined' && window.visualViewport) {
|
|
72836
|
+
window.visualViewport.removeEventListener('resize', this.vvListener);
|
|
72837
|
+
}
|
|
72838
|
+
}
|
|
72839
|
+
ngOnChanges(changes) {
|
|
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;
|
|
73309
72845
|
}
|
|
73310
|
-
}
|
|
72846
|
+
}
|
|
73311
72847
|
}
|
|
73312
|
-
|
|
73313
|
-
|
|
73314
|
-
|
|
73315
|
-
|
|
73316
|
-
|
|
73317
|
-
this.atBottom.set(nearBottom);
|
|
73318
|
-
if (nearBottom)
|
|
73319
|
-
this.hasNew.set(false);
|
|
73320
|
-
if (el.scrollTop < 40)
|
|
73321
|
-
this.loadMore.emit();
|
|
72848
|
+
ngAfterViewChecked() {
|
|
72849
|
+
if (this.shouldScrollToBottom) {
|
|
72850
|
+
this.scrollToBottom();
|
|
72851
|
+
this.shouldScrollToBottom = false;
|
|
72852
|
+
}
|
|
73322
72853
|
}
|
|
73323
|
-
|
|
73324
|
-
const el = this.
|
|
73325
|
-
if (!el)
|
|
72854
|
+
adjustToKeyboard() {
|
|
72855
|
+
const el = this.chatWindowElRef?.nativeElement;
|
|
72856
|
+
if (!el || !window.visualViewport)
|
|
73326
72857
|
return;
|
|
73327
|
-
|
|
73328
|
-
|
|
73329
|
-
|
|
72858
|
+
const rect = el.getBoundingClientRect();
|
|
72859
|
+
const available = window.visualViewport.height - rect.top;
|
|
72860
|
+
if (available > 50) {
|
|
72861
|
+
el.style.height = `${available}px`;
|
|
72862
|
+
}
|
|
73330
72863
|
}
|
|
73331
|
-
|
|
73332
|
-
this.
|
|
73333
|
-
this.replyingTo.set(null);
|
|
72864
|
+
onReactionClick(event) {
|
|
72865
|
+
this.reactionClick.emit({ msgId: event.msgId, token: event.token });
|
|
73334
72866
|
}
|
|
73335
|
-
|
|
73336
|
-
|
|
73337
|
-
|
|
73338
|
-
|
|
73339
|
-
|
|
73340
|
-
|
|
73341
|
-
|
|
73342
|
-
|
|
73343
|
-
|
|
73344
|
-
|
|
73345
|
-
case 'edit':
|
|
73346
|
-
this.editMessage.emit(event.msgId);
|
|
73347
|
-
break;
|
|
73348
|
-
case 'delete':
|
|
73349
|
-
this.deleteMessage.emit(event.msgId);
|
|
73350
|
-
break;
|
|
73351
|
-
case 'react':
|
|
73352
|
-
this.reactionClick.emit({ msgId: event.msgId, token: event.token ?? '👍' });
|
|
73353
|
-
break;
|
|
72867
|
+
onEditClick(_msgId) {
|
|
72868
|
+
// Implementacion futura: emitir evento de edicion
|
|
72869
|
+
}
|
|
72870
|
+
onTyping() {
|
|
72871
|
+
// El padre puede suscribirse al output typing si necesita propagar a Firestore
|
|
72872
|
+
}
|
|
72873
|
+
scrollToBottom() {
|
|
72874
|
+
const el = this.messagesListRef?.nativeElement;
|
|
72875
|
+
if (el) {
|
|
72876
|
+
el.scrollTop = el.scrollHeight;
|
|
73354
72877
|
}
|
|
73355
72878
|
}
|
|
73356
|
-
|
|
73357
|
-
|
|
72879
|
+
formatDateLabel(date) {
|
|
72880
|
+
if (!date)
|
|
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' });
|
|
73358
72893
|
}
|
|
73359
72894
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73360
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatWindowComponent, isStandalone: true, selector: "val-chat-window", inputs: { convId:
|
|
73361
|
-
<div class="chat">
|
|
73362
|
-
<div
|
|
73363
|
-
@if (isLoading
|
|
73364
|
-
<div class="state">
|
|
73365
|
-
<
|
|
73366
|
-
|
|
73367
|
-
} @else if (rows().length === 0) {
|
|
73368
|
-
<div class="state empty">
|
|
73369
|
-
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73370
|
-
<span>{{ t('noMessages') }}</span>
|
|
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", participants: "participants", currentUserId: "currentUserId", isOpen: "isOpen", typingUsers: "typingUsers", isLoading: "isLoading" }, outputs: { sendMessage: "sendMessage", loadMore: "loadMore", reactionClick: "reactionClick", deleteMessage: "deleteMessage", replyTo: "replyTo" }, viewQueries: [{ propertyName: "messagesListRef", first: true, predicate: ["messagesList"], descendants: true }, { propertyName: "chatWindowElRef", first: true, predicate: ["chatWindowEl"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
72896
|
+
<div class="chat-window" #chatWindowEl>
|
|
72897
|
+
<div class="messages-list" #messagesList>
|
|
72898
|
+
@if (isLoading) {
|
|
72899
|
+
<div class="loading-state">
|
|
72900
|
+
<ion-spinner name="dots"></ion-spinner>
|
|
72901
|
+
<span>{{ loadingText() }}</span>
|
|
73371
72902
|
</div>
|
|
73372
72903
|
} @else {
|
|
73373
|
-
@for (
|
|
73374
|
-
|
|
73375
|
-
<div class="date-
|
|
73376
|
-
|
|
73377
|
-
|
|
73378
|
-
|
|
72904
|
+
@for (group of messageGroups(); track group.dateLabel) {
|
|
72905
|
+
<div class="date-separator">
|
|
72906
|
+
<div class="date-line"></div>
|
|
72907
|
+
<span class="date-label">{{ group.dateLabel }}</span>
|
|
72908
|
+
<div class="date-line"></div>
|
|
72909
|
+
</div>
|
|
72910
|
+
@for (msg of group.messages; track msg.msgId) {
|
|
73379
72911
|
<val-message-bubble
|
|
73380
|
-
[
|
|
73381
|
-
|
|
73382
|
-
|
|
73383
|
-
|
|
73384
|
-
|
|
73385
|
-
(action)="onAction($event)"
|
|
72912
|
+
[props]="msg"
|
|
72913
|
+
(reactionClick)="onReactionClick($event)"
|
|
72914
|
+
(replyClick)="replyTo.emit($event.msgId)"
|
|
72915
|
+
(deleteClick)="deleteMessage.emit($event.msgId)"
|
|
72916
|
+
(editClick)="onEditClick($event.msgId)"
|
|
73386
72917
|
/>
|
|
73387
72918
|
}
|
|
73388
72919
|
}
|
|
73389
72920
|
}
|
|
73390
|
-
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
73391
72921
|
</div>
|
|
73392
72922
|
|
|
73393
|
-
|
|
73394
|
-
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73395
|
-
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73396
|
-
@if (hasNew()) {
|
|
73397
|
-
<span class="dot"></span>
|
|
73398
|
-
}
|
|
73399
|
-
</button>
|
|
73400
|
-
}
|
|
72923
|
+
<val-typing-indicator [typingUsers]="typingUsers" />
|
|
73401
72924
|
|
|
73402
|
-
@if (isOpen
|
|
73403
|
-
<
|
|
73404
|
-
[replyingTo]="replyingTo()"
|
|
73405
|
-
(send)="onSend($event)"
|
|
73406
|
-
(typing)="typing.emit()"
|
|
73407
|
-
(attach)="attach.emit($event)"
|
|
73408
|
-
(voice)="voice.emit()"
|
|
73409
|
-
(cancelReply)="replyingTo.set(null)"
|
|
73410
|
-
/>
|
|
72925
|
+
@if (!isOpen) {
|
|
72926
|
+
<div class="closed-banner">{{ closedText() }}</div>
|
|
73411
72927
|
} @else {
|
|
73412
|
-
<
|
|
72928
|
+
<val-chat-input [props]="{ disabled: isLoading }" (send)="sendMessage.emit($event)" (typing)="onTyping()" />
|
|
73413
72929
|
}
|
|
73414
72930
|
</div>
|
|
73415
|
-
`, isInline: true, styles: ["
|
|
72931
|
+
`, isInline: true, styles: [".chat-window{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8);overflow:hidden}.messages-list{flex:1;overflow-y:auto;padding:12px 8px;scroll-behavior:smooth}.messages-list::-webkit-scrollbar{width:4px}.messages-list::-webkit-scrollbar-thumb{background:var(--ion-color-light-shade, #d7d8da);border-radius:2px}.date-separator{display:flex;align-items:center;gap:8px;margin:12px 0}.date-separator .date-line{flex:1;height:1px;background:var(--ion-border-color, rgba(0, 0, 0, .1))}.date-separator .date-label{font-size:.75rem;color:var(--ion-color-medium, #92949c);white-space:nowrap;padding:0 4px}.loading-state{display:flex;align-items:center;justify-content:center;padding:24px;color:var(--ion-color-medium, #92949c);font-size:.875rem;gap:8px}.closed-banner{padding:10px 16px;background:var(--ion-color-warning-tint, #fff9e6);border-top:1px solid var(--ion-color-warning, #ffc409);text-align:center;font-size:.875rem;color:var(--ion-color-warning-shade, #b29500)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: MessageBubbleComponent, selector: "val-message-bubble", inputs: ["props"], outputs: ["reactionClick", "replyClick", "deleteClick", "editClick"] }, { kind: "component", type: ChatInputComponent, selector: "val-chat-input", inputs: ["props"], outputs: ["send", "typing"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
|
|
73416
72932
|
}
|
|
73417
72933
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
|
|
73418
72934
|
type: Component,
|
|
73419
|
-
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon, MessageBubbleComponent,
|
|
73420
|
-
<div class="chat">
|
|
73421
|
-
<div
|
|
73422
|
-
@if (isLoading
|
|
73423
|
-
<div class="state">
|
|
73424
|
-
<
|
|
73425
|
-
|
|
73426
|
-
} @else if (rows().length === 0) {
|
|
73427
|
-
<div class="state empty">
|
|
73428
|
-
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73429
|
-
<span>{{ t('noMessages') }}</span>
|
|
72935
|
+
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon, IonSpinner, MessageBubbleComponent, ChatInputComponent, TypingIndicatorComponent], template: `
|
|
72936
|
+
<div class="chat-window" #chatWindowEl>
|
|
72937
|
+
<div class="messages-list" #messagesList>
|
|
72938
|
+
@if (isLoading) {
|
|
72939
|
+
<div class="loading-state">
|
|
72940
|
+
<ion-spinner name="dots"></ion-spinner>
|
|
72941
|
+
<span>{{ loadingText() }}</span>
|
|
73430
72942
|
</div>
|
|
73431
72943
|
} @else {
|
|
73432
|
-
@for (
|
|
73433
|
-
|
|
73434
|
-
<div class="date-
|
|
73435
|
-
|
|
73436
|
-
|
|
73437
|
-
|
|
72944
|
+
@for (group of messageGroups(); track group.dateLabel) {
|
|
72945
|
+
<div class="date-separator">
|
|
72946
|
+
<div class="date-line"></div>
|
|
72947
|
+
<span class="date-label">{{ group.dateLabel }}</span>
|
|
72948
|
+
<div class="date-line"></div>
|
|
72949
|
+
</div>
|
|
72950
|
+
@for (msg of group.messages; track msg.msgId) {
|
|
73438
72951
|
<val-message-bubble
|
|
73439
|
-
[
|
|
73440
|
-
|
|
73441
|
-
|
|
73442
|
-
|
|
73443
|
-
|
|
73444
|
-
(action)="onAction($event)"
|
|
72952
|
+
[props]="msg"
|
|
72953
|
+
(reactionClick)="onReactionClick($event)"
|
|
72954
|
+
(replyClick)="replyTo.emit($event.msgId)"
|
|
72955
|
+
(deleteClick)="deleteMessage.emit($event.msgId)"
|
|
72956
|
+
(editClick)="onEditClick($event.msgId)"
|
|
73445
72957
|
/>
|
|
73446
72958
|
}
|
|
73447
72959
|
}
|
|
73448
72960
|
}
|
|
73449
|
-
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
73450
72961
|
</div>
|
|
73451
72962
|
|
|
73452
|
-
|
|
73453
|
-
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73454
|
-
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73455
|
-
@if (hasNew()) {
|
|
73456
|
-
<span class="dot"></span>
|
|
73457
|
-
}
|
|
73458
|
-
</button>
|
|
73459
|
-
}
|
|
72963
|
+
<val-typing-indicator [typingUsers]="typingUsers" />
|
|
73460
72964
|
|
|
73461
|
-
@if (isOpen
|
|
73462
|
-
<
|
|
73463
|
-
[replyingTo]="replyingTo()"
|
|
73464
|
-
(send)="onSend($event)"
|
|
73465
|
-
(typing)="typing.emit()"
|
|
73466
|
-
(attach)="attach.emit($event)"
|
|
73467
|
-
(voice)="voice.emit()"
|
|
73468
|
-
(cancelReply)="replyingTo.set(null)"
|
|
73469
|
-
/>
|
|
72965
|
+
@if (!isOpen) {
|
|
72966
|
+
<div class="closed-banner">{{ closedText() }}</div>
|
|
73470
72967
|
} @else {
|
|
73471
|
-
<
|
|
72968
|
+
<val-chat-input [props]="{ disabled: isLoading }" (send)="sendMessage.emit($event)" (typing)="onTyping()" />
|
|
73472
72969
|
}
|
|
73473
72970
|
</div>
|
|
73474
|
-
`, styles: ["
|
|
73475
|
-
}], ctorParameters: () => []
|
|
72971
|
+
`, styles: [".chat-window{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8);overflow:hidden}.messages-list{flex:1;overflow-y:auto;padding:12px 8px;scroll-behavior:smooth}.messages-list::-webkit-scrollbar{width:4px}.messages-list::-webkit-scrollbar-thumb{background:var(--ion-color-light-shade, #d7d8da);border-radius:2px}.date-separator{display:flex;align-items:center;gap:8px;margin:12px 0}.date-separator .date-line{flex:1;height:1px;background:var(--ion-border-color, rgba(0, 0, 0, .1))}.date-separator .date-label{font-size:.75rem;color:var(--ion-color-medium, #92949c);white-space:nowrap;padding:0 4px}.loading-state{display:flex;align-items:center;justify-content:center;padding:24px;color:var(--ion-color-medium, #92949c);font-size:.875rem;gap:8px}.closed-banner{padding:10px 16px;background:var(--ion-color-warning-tint, #fff9e6);border-top:1px solid var(--ion-color-warning, #ffc409);text-align:center;font-size:.875rem;color:var(--ion-color-warning-shade, #b29500)}\n"] }]
|
|
72972
|
+
}], ctorParameters: () => [], propDecorators: { convId: [{
|
|
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
|
+
}] } });
|
|
73476
73003
|
|
|
73477
73004
|
const THREAD_PANEL_I18N = {
|
|
73478
73005
|
es: {
|
|
@@ -73515,7 +73042,7 @@ class ThreadPanelComponent {
|
|
|
73515
73042
|
this.conv = signal(null);
|
|
73516
73043
|
this.messages = signal([]);
|
|
73517
73044
|
this.typingUsers = signal([]);
|
|
73518
|
-
this.isConvOpen = computed(() => this.conv()?.
|
|
73045
|
+
this.isConvOpen = computed(() => this.conv()?.status === 'open');
|
|
73519
73046
|
this.loadingText = computed(() => {
|
|
73520
73047
|
this.i18n.lang();
|
|
73521
73048
|
return this.i18n.t('loading', 'ThreadPanel');
|
|
@@ -73553,10 +73080,10 @@ class ThreadPanelComponent {
|
|
|
73553
73080
|
const appId = this.chatConfig?.appId ?? '';
|
|
73554
73081
|
if (orgId && appId) {
|
|
73555
73082
|
this.msgSub = this.conversationSvc
|
|
73556
|
-
.watchMessages(conversation.
|
|
73083
|
+
.watchMessages(conversation.id, orgId, appId)
|
|
73557
73084
|
.subscribe(msgs => this.messages.set(msgs));
|
|
73558
73085
|
this.typingSub = this.conversationSvc
|
|
73559
|
-
.watchTyping(conversation.
|
|
73086
|
+
.watchTyping(conversation.id, orgId, appId)
|
|
73560
73087
|
.subscribe(users => this.typingUsers.set(users));
|
|
73561
73088
|
}
|
|
73562
73089
|
}
|
|
@@ -73574,14 +73101,14 @@ class ThreadPanelComponent {
|
|
|
73574
73101
|
const orgId = this.getOrgId();
|
|
73575
73102
|
const appId = this.chatConfig?.appId ?? '';
|
|
73576
73103
|
if (conv && orgId && appId && this.conversationSvc) {
|
|
73577
|
-
this.conversationSvc.stopTyping(conv.
|
|
73104
|
+
this.conversationSvc.stopTyping(conv.id, orgId, appId);
|
|
73578
73105
|
}
|
|
73579
73106
|
}
|
|
73580
73107
|
onSend(body) {
|
|
73581
73108
|
const conv = this.conv();
|
|
73582
73109
|
if (!conv || !this.conversationSvc)
|
|
73583
73110
|
return;
|
|
73584
|
-
this.conversationSvc.sendMessage(conv.
|
|
73111
|
+
this.conversationSvc.sendMessage(conv.id, body).subscribe();
|
|
73585
73112
|
}
|
|
73586
73113
|
onReactionClick(event) {
|
|
73587
73114
|
// Reacciones se implementan a nivel de app consumer
|
|
@@ -73591,7 +73118,7 @@ class ThreadPanelComponent {
|
|
|
73591
73118
|
const conv = this.conv();
|
|
73592
73119
|
if (!conv || !this.conversationSvc)
|
|
73593
73120
|
return;
|
|
73594
|
-
this.conversationSvc.deleteMessage(conv.
|
|
73121
|
+
this.conversationSvc.deleteMessage(conv.id, msgId).subscribe();
|
|
73595
73122
|
}
|
|
73596
73123
|
onReplyTo(_msgId) {
|
|
73597
73124
|
// Implementacion futura: seleccionar mensaje de respuesta
|
|
@@ -73616,7 +73143,7 @@ class ThreadPanelComponent {
|
|
|
73616
73143
|
} @else {
|
|
73617
73144
|
<div class="thread-chat">
|
|
73618
73145
|
<val-chat-window
|
|
73619
|
-
[convId]="conv()?.
|
|
73146
|
+
[convId]="conv()?.id ?? ''"
|
|
73620
73147
|
[messages]="bubbleMessages()"
|
|
73621
73148
|
[currentUserId]="currentUserId"
|
|
73622
73149
|
[isOpen]="isConvOpen()"
|
|
@@ -73630,7 +73157,7 @@ class ThreadPanelComponent {
|
|
|
73630
73157
|
</div>
|
|
73631
73158
|
}
|
|
73632
73159
|
</div>
|
|
73633
|
-
`, isInline: true, styles: [".thread-panel{display:flex;flex-direction:column;height:100%;background:var(--ion-background-color, #f4f5f8)}.thread-header{padding:12px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff))}.thread-header .thread-title{font-size:1rem;font-weight:600;color:var(--ion-text-color, #000);margin:0}.thread-loading{display:flex;align-items:center;justify-content:center;flex:1;gap:10px;color:var(--ion-color-medium, #92949c);font-size:.875rem}.thread-chat{flex:1;overflow:hidden;display:flex;flex-direction:column}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: ChatWindowComponent, selector: "val-chat-window", inputs: ["convId", "messages", "currentUserId", "isOpen", "typingUsers", "isLoading"], outputs: ["sendMessage", "loadMore", "reactionClick", "deleteMessage", "replyTo"
|
|
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", "participants", "currentUserId", "isOpen", "typingUsers", "isLoading"], outputs: ["sendMessage", "loadMore", "reactionClick", "deleteMessage", "replyTo"] }] }); }
|
|
73634
73161
|
}
|
|
73635
73162
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThreadPanelComponent, decorators: [{
|
|
73636
73163
|
type: Component,
|
|
@@ -73650,7 +73177,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73650
73177
|
} @else {
|
|
73651
73178
|
<div class="thread-chat">
|
|
73652
73179
|
<val-chat-window
|
|
73653
|
-
[convId]="conv()?.
|
|
73180
|
+
[convId]="conv()?.id ?? ''"
|
|
73654
73181
|
[messages]="bubbleMessages()"
|
|
73655
73182
|
[currentUserId]="currentUserId"
|
|
73656
73183
|
[isOpen]="isConvOpen()"
|
|
@@ -73684,5 +73211,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73684
73211
|
* Generated bundle index. Do not edit.
|
|
73685
73212
|
*/
|
|
73686
73213
|
|
|
73687
|
-
export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, APP_VERSION_PLATFORM_PLUGIN, APP_VERSION_REMOTE_PLUGIN, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AboutViewComponent, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, ApiKeyCreateModalComponent, ApiKeyService, ApiKeysModalComponent, ApiKeysViewComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, ChatComposerComponent, ChatInputComponent, ChatWindowComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, ConversationListItemComponent, ConversationService, CookieBannerComponent, CookieSettingsComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_BUTTON_PRESETS, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_LOGIN_LOGO, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_POST_UPDATE_GRACE_MS, DEFAULT_PRESETS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DatePickerComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmptyStateComponent, EntityCardComponent, EntityFeedService, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FieldListComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeSelectorComponent, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormFieldComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HapticsService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HrefComponent, HtmlViewerModalComponent, I18nService, IMAGE_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, IONIC_COLORS$5 as IONIC_COLORS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGGED_IN_HINT_COOKIE, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LanguageSelectorV2Component, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadMoreComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MemberImportModalComponent, MenuComponent, MessageBubbleComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationPreferencesViewComponent, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OptionCardsComponent, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, POST_UPDATE_TS_KEY, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PdfService, PermissionCatalogService, PermissionSelectorComponent, PermissionsModalComponent, PermissionsViewComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QrScannerComponent, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, ReactionBarComponent, ReactionsService, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestFormComponent, RequestModalComponent, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SectionHeaderComponent, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SplashComponent, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, ThreadPanelComponent, TicketCardComponent, TicketCardImageService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, TypingIndicatorComponent, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_CHAT_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_REACTIONS_CONFIG, VALTECH_SETTINGS_MENU_LINKS, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VALTECH_WHATSAPP_CONFIG, VERSION, ValtechErrorService, VerifyViewComponent, WhatsappFabComponent, WhatsappService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, button, canSubmitRequestType, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createPageState, createPermissionLabeler, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, formatClockTime, formatDateSeparator, formatRelativeTime, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, groupPermissionsByScope, guestGuard, hasEmulators, iconButton, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isIonicColor, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAboutRoutes, provideValtechAccountRoutes, provideValtechAds, provideValtechApiKeysRoutes, provideValtechAppConfig, provideValtechAppVersion, provideValtechAppVersionHttp, provideValtechAuth, provideValtechAuthInterceptor, provideValtechChat, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationPreferencesRoutes, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPermissionsRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechReactions, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, provideValtechWhatsapp, query, rbacGuard, renderPatternSvgInner, replaceSpecialChars, requestSubmitMode, resolveColor, resolveInputDefaultValue, resolveIonicColor, resolveWebBaseUrl, roleGuard, selectableRequestTypes, storagePaths, superAdminGuard, toArticle };
|
|
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 };
|
|
73688
73215
|
//# sourceMappingURL=valtech-components.mjs.map
|