valtech-components 4.0.227 → 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 -274
- 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 +14 -37
- 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 +428 -1080
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/message-bubble/message-bubble.component.d.ts +32 -34
- 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 +5 -12
- 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 -391
- 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 -63
- 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.
|
|
@@ -72171,21 +72102,10 @@ class ConversationService {
|
|
|
72171
72102
|
return this.http.delete(`${this.baseUrl}/conversations/${convId}/participants/${userId}`);
|
|
72172
72103
|
}
|
|
72173
72104
|
/**
|
|
72174
|
-
* Envia un mensaje a una conversacion
|
|
72175
|
-
* El backend espera `attachments: { url, type, name, size }[]` (key `type` = MIME),
|
|
72176
|
-
* mientras que el front usa `mimeType` — se mapea aqui.
|
|
72105
|
+
* Envia un mensaje a una conversacion.
|
|
72177
72106
|
*/
|
|
72178
|
-
sendMessage(convId, body
|
|
72179
|
-
|
|
72180
|
-
if (attachments?.length) {
|
|
72181
|
-
payload['attachments'] = attachments.map(a => ({
|
|
72182
|
-
url: a.url,
|
|
72183
|
-
type: a.mimeType,
|
|
72184
|
-
name: a.name,
|
|
72185
|
-
size: a.size ?? 0,
|
|
72186
|
-
}));
|
|
72187
|
-
}
|
|
72188
|
-
return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, payload);
|
|
72107
|
+
sendMessage(convId, body) {
|
|
72108
|
+
return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, { body });
|
|
72189
72109
|
}
|
|
72190
72110
|
/**
|
|
72191
72111
|
* Elimina (marca como eliminado) un mensaje.
|
|
@@ -72354,323 +72274,241 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72354
72274
|
*/
|
|
72355
72275
|
|
|
72356
72276
|
addIcons({
|
|
72357
|
-
alertCircle,
|
|
72358
72277
|
checkmark,
|
|
72359
72278
|
checkmarkDone,
|
|
72360
|
-
createOutline,
|
|
72361
|
-
documentOutline,
|
|
72362
|
-
happyOutline,
|
|
72363
|
-
arrowUndoOutline,
|
|
72364
72279
|
timeOutline,
|
|
72365
|
-
|
|
72280
|
+
alertCircle,
|
|
72281
|
+
pencil,
|
|
72282
|
+
trash,
|
|
72283
|
+
returnDownBackOutline,
|
|
72366
72284
|
});
|
|
72367
|
-
const
|
|
72285
|
+
const CHAT_I18N = {
|
|
72368
72286
|
es: {
|
|
72287
|
+
messageDeleted: '[mensaje eliminado]',
|
|
72288
|
+
edited: '(editado)',
|
|
72289
|
+
react: 'Reaccionar',
|
|
72369
72290
|
reply: 'Responder',
|
|
72370
72291
|
edit: 'Editar',
|
|
72371
72292
|
delete: 'Eliminar',
|
|
72372
|
-
react: 'Reaccionar',
|
|
72373
|
-
deleted: 'Mensaje eliminado',
|
|
72374
|
-
edited: 'editado',
|
|
72375
72293
|
},
|
|
72376
72294
|
en: {
|
|
72295
|
+
messageDeleted: '[message deleted]',
|
|
72296
|
+
edited: '(edited)',
|
|
72297
|
+
react: 'React',
|
|
72377
72298
|
reply: 'Reply',
|
|
72378
72299
|
edit: 'Edit',
|
|
72379
72300
|
delete: 'Delete',
|
|
72380
|
-
react: 'React',
|
|
72381
|
-
deleted: 'Message deleted',
|
|
72382
|
-
edited: 'edited',
|
|
72383
72301
|
},
|
|
72384
72302
|
};
|
|
72385
72303
|
/**
|
|
72386
|
-
* val-message-bubble
|
|
72304
|
+
* val-message-bubble
|
|
72387
72305
|
*
|
|
72388
|
-
*
|
|
72389
|
-
*
|
|
72390
|
-
*
|
|
72391
|
-
* 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.
|
|
72392
72309
|
*
|
|
72393
|
-
*
|
|
72394
|
-
*
|
|
72310
|
+
* @example
|
|
72311
|
+
* <val-message-bubble
|
|
72312
|
+
* [props]="msg"
|
|
72313
|
+
* (reactionClick)="onReaction($event)"
|
|
72314
|
+
* (replyClick)="onReply($event)"
|
|
72315
|
+
* (deleteClick)="onDelete($event)"
|
|
72316
|
+
* />
|
|
72395
72317
|
*/
|
|
72396
72318
|
class MessageBubbleComponent {
|
|
72397
72319
|
constructor() {
|
|
72398
72320
|
this.i18n = inject(I18nService);
|
|
72399
|
-
this.
|
|
72400
|
-
this.
|
|
72401
|
-
this.
|
|
72402
|
-
this.
|
|
72403
|
-
this.
|
|
72404
|
-
|
|
72405
|
-
|
|
72406
|
-
this.time = computed(() => formatClockTime(this.msg().createdAt, this.locale()));
|
|
72407
|
-
this.initials = computed(() => {
|
|
72408
|
-
const name = this.msg().senderName?.trim() ?? '';
|
|
72409
|
-
if (!name)
|
|
72410
|
-
return '?';
|
|
72411
|
-
const parts = name.split(/\s+/);
|
|
72412
|
-
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
72413
|
-
});
|
|
72414
|
-
this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
|
|
72415
|
-
this.audioAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('audio/')));
|
|
72416
|
-
this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/') && !a.mimeType?.startsWith('audio/')));
|
|
72417
|
-
this.statusIcon = computed(() => {
|
|
72418
|
-
switch (this.msg().status) {
|
|
72419
|
-
case 'sending':
|
|
72420
|
-
return 'time-outline';
|
|
72421
|
-
case 'failed':
|
|
72422
|
-
return 'alert-circle';
|
|
72423
|
-
case 'delivered':
|
|
72424
|
-
case 'read':
|
|
72425
|
-
return 'checkmark-done';
|
|
72426
|
-
case 'sent':
|
|
72427
|
-
default:
|
|
72428
|
-
return 'checkmark';
|
|
72429
|
-
}
|
|
72430
|
-
});
|
|
72431
|
-
this.i18n.registerDefaults('MessageBubble', MESSAGE_BUBBLE_I18N);
|
|
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
|
+
}
|
|
72432
72328
|
}
|
|
72433
|
-
|
|
72434
|
-
|
|
72435
|
-
|
|
72436
|
-
this.
|
|
72329
|
+
ngOnInit() { }
|
|
72330
|
+
deletedText() {
|
|
72331
|
+
this.i18n.lang();
|
|
72332
|
+
return this.i18n.t('messageDeleted', 'Chat');
|
|
72437
72333
|
}
|
|
72438
|
-
|
|
72439
|
-
this.
|
|
72440
|
-
this.
|
|
72334
|
+
editedLabel() {
|
|
72335
|
+
this.i18n.lang();
|
|
72336
|
+
return this.i18n.t('edited', 'Chat');
|
|
72441
72337
|
}
|
|
72442
|
-
|
|
72443
|
-
|
|
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 });
|
|
72444
72366
|
}
|
|
72445
72367
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
72446
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: MessageBubbleComponent, isStandalone: true, selector: "val-message-bubble", inputs: {
|
|
72447
|
-
<div class="
|
|
72448
|
-
@if (
|
|
72449
|
-
<
|
|
72450
|
-
@if (msg().senderAvatar) {
|
|
72451
|
-
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72452
|
-
} @else {
|
|
72453
|
-
<span>{{ initials() }}</span>
|
|
72454
|
-
}
|
|
72455
|
-
</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>
|
|
72456
72372
|
}
|
|
72457
72373
|
|
|
72458
|
-
<div class="bubble
|
|
72459
|
-
|
|
72460
|
-
class="
|
|
72461
|
-
|
|
72462
|
-
|
|
72463
|
-
|
|
72464
|
-
|
|
72465
|
-
>
|
|
72466
|
-
@if (showName() && !msg().isMine && !msg().isDeleted) {
|
|
72467
|
-
<span class="name">{{ msg().senderName }}</span>
|
|
72468
|
-
}
|
|
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
|
+
}
|
|
72469
72381
|
|
|
72470
|
-
|
|
72471
|
-
|
|
72472
|
-
|
|
72473
|
-
|
|
72474
|
-
|
|
72475
|
-
}
|
|
72382
|
+
@if (props.isDeleted) {
|
|
72383
|
+
<span class="deleted-text">{{ deletedText() }}</span>
|
|
72384
|
+
} @else {
|
|
72385
|
+
<div class="bubble-body">{{ props.body }}</div>
|
|
72386
|
+
}
|
|
72476
72387
|
|
|
72477
|
-
|
|
72478
|
-
|
|
72479
|
-
|
|
72480
|
-
|
|
72481
|
-
|
|
72482
|
-
|
|
72483
|
-
|
|
72484
|
-
|
|
72485
|
-
|
|
72486
|
-
|
|
72487
|
-
|
|
72488
|
-
<div class="audios">
|
|
72489
|
-
@for (att of audioAttachments(); track att.url) {
|
|
72490
|
-
<audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
|
|
72491
|
-
}
|
|
72492
|
-
</div>
|
|
72493
|
-
}
|
|
72494
|
-
@if (fileAttachments().length > 0) {
|
|
72495
|
-
<div class="files">
|
|
72496
|
-
@for (att of fileAttachments(); track att.url) {
|
|
72497
|
-
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72498
|
-
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72499
|
-
<span>{{ att.name }}</span>
|
|
72500
|
-
</a>
|
|
72501
|
-
}
|
|
72502
|
-
</div>
|
|
72503
|
-
}
|
|
72504
|
-
@if (msg().body) {
|
|
72505
|
-
<span class="body">{{ msg().body }}</span>
|
|
72506
|
-
}
|
|
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>
|
|
72507
72399
|
}
|
|
72508
|
-
|
|
72509
|
-
<span class="meta">
|
|
72510
|
-
@if (msg().isEdited && !msg().isDeleted) {
|
|
72511
|
-
<span class="edited">{{ t('edited') }}</span>
|
|
72512
|
-
}
|
|
72513
|
-
<span class="time">{{ time() }}</span>
|
|
72514
|
-
@if (msg().isMine && !msg().isDeleted) {
|
|
72515
|
-
<ion-icon
|
|
72516
|
-
class="status"
|
|
72517
|
-
[class.read]="msg().status === 'read'"
|
|
72518
|
-
[class.failed]="msg().status === 'failed'"
|
|
72519
|
-
[name]="statusIcon()"
|
|
72520
|
-
aria-hidden="true"
|
|
72521
|
-
/>
|
|
72522
|
-
}
|
|
72523
|
-
</span>
|
|
72524
72400
|
</div>
|
|
72401
|
+
</div>
|
|
72525
72402
|
|
|
72526
|
-
|
|
72527
|
-
|
|
72528
|
-
|
|
72529
|
-
|
|
72530
|
-
|
|
72531
|
-
|
|
72532
|
-
|
|
72533
|
-
|
|
72534
|
-
</div>
|
|
72535
|
-
}
|
|
72536
|
-
|
|
72537
|
-
@if (!msg().isDeleted) {
|
|
72538
|
-
<div class="actions" [class.open]="actionsOpen()">
|
|
72539
|
-
<button class="act" [attr.aria-label]="t('react')" (click)="emit('react', '👍')">
|
|
72540
|
-
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
72541
|
-
</button>
|
|
72542
|
-
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72543
|
-
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
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
|
+
}
|
|
72544
72411
|
</button>
|
|
72545
|
-
@if (msg().isMine) {
|
|
72546
|
-
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72547
|
-
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72548
|
-
</button>
|
|
72549
|
-
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72550
|
-
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72551
|
-
</button>
|
|
72552
|
-
}
|
|
72553
|
-
</div>
|
|
72554
|
-
}
|
|
72555
|
-
</div>
|
|
72556
|
-
</div>
|
|
72557
|
-
`, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.audios{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-audio{width:240px;max-width:100%;height:40px}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
72558
|
-
}
|
|
72559
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72560
|
-
type: Component,
|
|
72561
|
-
args: [{ selector: 'val-message-bubble', standalone: true, imports: [CommonModule, IonIcon], template: `
|
|
72562
|
-
<div class="row" [class.mine]="msg().isMine" [class.tail]="tail()" [class.with-avatar]="showAvatar()">
|
|
72563
|
-
@if (showAvatar() && !msg().isMine) {
|
|
72564
|
-
<div class="avatar" [class.hidden]="!tail()">
|
|
72565
|
-
@if (msg().senderAvatar) {
|
|
72566
|
-
<img [src]="msg().senderAvatar" [alt]="msg().senderName" />
|
|
72567
|
-
} @else {
|
|
72568
|
-
<span>{{ initials() }}</span>
|
|
72569
72412
|
}
|
|
72570
72413
|
</div>
|
|
72571
72414
|
}
|
|
72572
72415
|
|
|
72573
|
-
|
|
72574
|
-
<div
|
|
72575
|
-
|
|
72576
|
-
|
|
72577
|
-
|
|
72578
|
-
(
|
|
72579
|
-
|
|
72580
|
-
|
|
72581
|
-
|
|
72582
|
-
<
|
|
72583
|
-
|
|
72584
|
-
|
|
72585
|
-
@if (msg().replyTo && !msg().isDeleted) {
|
|
72586
|
-
<div class="reply-preview">
|
|
72587
|
-
<span class="reply-name">{{ msg().replyTo!.senderName }}</span>
|
|
72588
|
-
<span class="reply-body">{{ msg().replyTo!.body }}</span>
|
|
72589
|
-
</div>
|
|
72590
|
-
}
|
|
72591
|
-
|
|
72592
|
-
@if (msg().isDeleted) {
|
|
72593
|
-
<span class="body deleted-text">{{ t('deleted') }}</span>
|
|
72594
|
-
} @else {
|
|
72595
|
-
@if (imageAttachments().length > 0) {
|
|
72596
|
-
<div class="attachments">
|
|
72597
|
-
@for (att of imageAttachments(); track att.url) {
|
|
72598
|
-
<img class="att-image" [src]="att.url" [alt]="att.name" />
|
|
72599
|
-
}
|
|
72600
|
-
</div>
|
|
72601
|
-
}
|
|
72602
|
-
@if (audioAttachments().length > 0) {
|
|
72603
|
-
<div class="audios">
|
|
72604
|
-
@for (att of audioAttachments(); track att.url) {
|
|
72605
|
-
<audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
|
|
72606
|
-
}
|
|
72607
|
-
</div>
|
|
72608
|
-
}
|
|
72609
|
-
@if (fileAttachments().length > 0) {
|
|
72610
|
-
<div class="files">
|
|
72611
|
-
@for (att of fileAttachments(); track att.url) {
|
|
72612
|
-
<a class="file-chip" [href]="att.url" target="_blank" rel="noopener">
|
|
72613
|
-
<ion-icon name="document-outline" aria-hidden="true" />
|
|
72614
|
-
<span>{{ att.name }}</span>
|
|
72615
|
-
</a>
|
|
72616
|
-
}
|
|
72617
|
-
</div>
|
|
72618
|
-
}
|
|
72619
|
-
@if (msg().body) {
|
|
72620
|
-
<span class="body">{{ msg().body }}</span>
|
|
72621
|
-
}
|
|
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>
|
|
72622
72428
|
}
|
|
72623
|
-
|
|
72624
|
-
<span class="meta">
|
|
72625
|
-
@if (msg().isEdited && !msg().isDeleted) {
|
|
72626
|
-
<span class="edited">{{ t('edited') }}</span>
|
|
72627
|
-
}
|
|
72628
|
-
<span class="time">{{ time() }}</span>
|
|
72629
|
-
@if (msg().isMine && !msg().isDeleted) {
|
|
72630
|
-
<ion-icon
|
|
72631
|
-
class="status"
|
|
72632
|
-
[class.read]="msg().status === 'read'"
|
|
72633
|
-
[class.failed]="msg().status === 'failed'"
|
|
72634
|
-
[name]="statusIcon()"
|
|
72635
|
-
aria-hidden="true"
|
|
72636
|
-
/>
|
|
72637
|
-
}
|
|
72638
|
-
</span>
|
|
72639
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
|
+
}
|
|
72640
72441
|
|
|
72641
|
-
|
|
72642
|
-
|
|
72643
|
-
|
|
72644
|
-
|
|
72645
|
-
|
|
72646
|
-
<span class="count">{{ r.count }}</span>
|
|
72647
|
-
</button>
|
|
72648
|
-
}
|
|
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>
|
|
72649
72447
|
</div>
|
|
72650
72448
|
}
|
|
72651
72449
|
|
|
72652
|
-
@if (
|
|
72653
|
-
<
|
|
72654
|
-
|
|
72655
|
-
|
|
72656
|
-
</button>
|
|
72657
|
-
<button class="act" [attr.aria-label]="t('reply')" (click)="emit('reply')">
|
|
72658
|
-
<ion-icon name="arrow-undo-outline" aria-hidden="true" />
|
|
72659
|
-
</button>
|
|
72660
|
-
@if (msg().isMine) {
|
|
72661
|
-
<button class="act" [attr.aria-label]="t('edit')" (click)="emit('edit')">
|
|
72662
|
-
<ion-icon name="create-outline" aria-hidden="true" />
|
|
72663
|
-
</button>
|
|
72664
|
-
<button class="act danger" [attr.aria-label]="t('delete')" (click)="emit('delete')">
|
|
72665
|
-
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
72666
|
-
</button>
|
|
72667
|
-
}
|
|
72668
|
-
</div>
|
|
72450
|
+
@if (props.isDeleted) {
|
|
72451
|
+
<span class="deleted-text">{{ deletedText() }}</span>
|
|
72452
|
+
} @else {
|
|
72453
|
+
<div class="bubble-body">{{ props.body }}</div>
|
|
72669
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>
|
|
72670
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
|
+
}
|
|
72671
72499
|
</div>
|
|
72672
|
-
`, styles: ["
|
|
72673
|
-
}], 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
|
+
}] } });
|
|
72674
72512
|
|
|
72675
72513
|
addIcons({ sendOutline });
|
|
72676
72514
|
const CHAT_INPUT_I18N = {
|
|
@@ -72763,6 +72601,10 @@ class ChatInputComponent {
|
|
|
72763
72601
|
[autoGrow]="true"
|
|
72764
72602
|
[rows]="1"
|
|
72765
72603
|
[value]="body()"
|
|
72604
|
+
enterkeyhint="send"
|
|
72605
|
+
autocomplete="off"
|
|
72606
|
+
autocorrect="off"
|
|
72607
|
+
spellcheck="false"
|
|
72766
72608
|
(ionInput)="onInput($event)"
|
|
72767
72609
|
(keydown)="onKeydown($event)"
|
|
72768
72610
|
></ion-textarea>
|
|
@@ -72795,6 +72637,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72795
72637
|
[autoGrow]="true"
|
|
72796
72638
|
[rows]="1"
|
|
72797
72639
|
[value]="body()"
|
|
72640
|
+
enterkeyhint="send"
|
|
72641
|
+
autocomplete="off"
|
|
72642
|
+
autocorrect="off"
|
|
72643
|
+
spellcheck="false"
|
|
72798
72644
|
(ionInput)="onInput($event)"
|
|
72799
72645
|
(keydown)="onKeydown($event)"
|
|
72800
72646
|
></ion-textarea>
|
|
@@ -72822,493 +72668,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72822
72668
|
type: Output
|
|
72823
72669
|
}] } });
|
|
72824
72670
|
|
|
72825
|
-
addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline, trashOutline });
|
|
72826
|
-
const CHAT_COMPOSER_I18N = {
|
|
72827
|
-
es: {
|
|
72828
|
-
placeholder: 'Escribe un mensaje',
|
|
72829
|
-
send: 'Enviar',
|
|
72830
|
-
attach: 'Adjuntar',
|
|
72831
|
-
emoji: 'Emoji',
|
|
72832
|
-
voice: 'Mensaje de voz',
|
|
72833
|
-
replyingTo: 'Respondiendo a',
|
|
72834
|
-
cancel: 'Cancelar',
|
|
72835
|
-
},
|
|
72836
|
-
en: {
|
|
72837
|
-
placeholder: 'Write a message',
|
|
72838
|
-
send: 'Send',
|
|
72839
|
-
attach: 'Attach',
|
|
72840
|
-
emoji: 'Emoji',
|
|
72841
|
-
voice: 'Voice message',
|
|
72842
|
-
replyingTo: 'Replying to',
|
|
72843
|
-
cancel: 'Cancel',
|
|
72844
|
-
},
|
|
72845
|
-
};
|
|
72846
|
-
const QUICK_EMOJIS = ['👍', '❤️', '😂', '🎉', '🙏', '🔥', '😮', '😢'];
|
|
72847
|
-
/**
|
|
72848
|
-
* val-chat-composer — barra de redacción de chat moderna (signal inputs).
|
|
72849
|
-
*
|
|
72850
|
-
* Pill con: adjuntar (+), textarea auto-grow, emoji (fila rápida) y mic/enviar
|
|
72851
|
-
* (mic cuando está vacío, enviar cuando hay texto — patrón WhatsApp/Telegram).
|
|
72852
|
-
* Muestra una barra de contexto "respondiendo a" cuando `replyingTo` está seteado.
|
|
72853
|
-
* `typing` se emite con debounce (máx. 1 vez cada 2s) para no spamear writes.
|
|
72854
|
-
*/
|
|
72855
|
-
class ChatComposerComponent {
|
|
72856
|
-
constructor() {
|
|
72857
|
-
this.i18n = inject(I18nService);
|
|
72858
|
-
this.placeholder = input('');
|
|
72859
|
-
this.disabled = input(false);
|
|
72860
|
-
this.maxLength = input(2000);
|
|
72861
|
-
this.replyingTo = input(null);
|
|
72862
|
-
this.showAttach = input(true);
|
|
72863
|
-
this.showEmoji = input(true);
|
|
72864
|
-
this.showMic = input(true);
|
|
72865
|
-
this.send = output();
|
|
72866
|
-
this.typing = output();
|
|
72867
|
-
this.attach = output();
|
|
72868
|
-
this.voice = output();
|
|
72869
|
-
this.cancelReply = output();
|
|
72870
|
-
this.body = signal('');
|
|
72871
|
-
this.emojiOpen = signal(false);
|
|
72872
|
-
this.quickEmojis = QUICK_EMOJIS;
|
|
72873
|
-
// Grabación de audio (MediaRecorder).
|
|
72874
|
-
this.recording = signal(false);
|
|
72875
|
-
this.recSeconds = signal(0);
|
|
72876
|
-
this.recChunks = [];
|
|
72877
|
-
this.recTimeLabel = computed(() => {
|
|
72878
|
-
const s = this.recSeconds();
|
|
72879
|
-
const mm = String(Math.floor(s / 60)).padStart(2, '0');
|
|
72880
|
-
const ss = String(s % 60).padStart(2, '0');
|
|
72881
|
-
return `${mm}:${ss}`;
|
|
72882
|
-
});
|
|
72883
|
-
this.lastTypingEmit = 0;
|
|
72884
|
-
this.canSend = computed(() => {
|
|
72885
|
-
const t = this.body().trim();
|
|
72886
|
-
return t.length > 0 && t.length <= this.maxLength() && !this.disabled();
|
|
72887
|
-
});
|
|
72888
|
-
this.placeholderText = computed(() => {
|
|
72889
|
-
this.i18n.lang();
|
|
72890
|
-
return this.placeholder() || this.i18n.t('placeholder', 'ChatComposer');
|
|
72891
|
-
});
|
|
72892
|
-
this.i18n.registerDefaults('ChatComposer', CHAT_COMPOSER_I18N);
|
|
72893
|
-
}
|
|
72894
|
-
onInput(event) {
|
|
72895
|
-
const target = event.target;
|
|
72896
|
-
this.body.set(target.value ?? '');
|
|
72897
|
-
this.emitTypingDebounced();
|
|
72898
|
-
}
|
|
72899
|
-
onKeydown(event) {
|
|
72900
|
-
if (event.key === 'Enter' && !event.shiftKey) {
|
|
72901
|
-
event.preventDefault();
|
|
72902
|
-
this.onSend();
|
|
72903
|
-
}
|
|
72904
|
-
}
|
|
72905
|
-
onSend() {
|
|
72906
|
-
const trimmed = this.body().trim();
|
|
72907
|
-
if (!trimmed || trimmed.length > this.maxLength())
|
|
72908
|
-
return;
|
|
72909
|
-
this.send.emit(trimmed);
|
|
72910
|
-
this.body.set('');
|
|
72911
|
-
this.emojiOpen.set(false);
|
|
72912
|
-
}
|
|
72913
|
-
toggleEmoji() {
|
|
72914
|
-
this.emojiOpen.update(v => !v);
|
|
72915
|
-
}
|
|
72916
|
-
insertEmoji(emoji) {
|
|
72917
|
-
this.body.update(v => v + emoji);
|
|
72918
|
-
}
|
|
72919
|
-
onFile(event) {
|
|
72920
|
-
const input = event.target;
|
|
72921
|
-
const file = input.files?.[0];
|
|
72922
|
-
if (file)
|
|
72923
|
-
this.attach.emit(file);
|
|
72924
|
-
input.value = '';
|
|
72925
|
-
}
|
|
72926
|
-
/** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
|
|
72927
|
-
onPaste(event) {
|
|
72928
|
-
const items = event.clipboardData?.items;
|
|
72929
|
-
if (!items)
|
|
72930
|
-
return;
|
|
72931
|
-
for (const item of Array.from(items)) {
|
|
72932
|
-
if (item.type.startsWith('image/')) {
|
|
72933
|
-
const file = item.getAsFile();
|
|
72934
|
-
if (file) {
|
|
72935
|
-
event.preventDefault();
|
|
72936
|
-
this.attach.emit(file);
|
|
72937
|
-
}
|
|
72938
|
-
return;
|
|
72939
|
-
}
|
|
72940
|
-
}
|
|
72941
|
-
}
|
|
72942
|
-
/**
|
|
72943
|
-
* Emite typing como máximo 1 vez cada 2s. No usa Date.now() directo en plantillas
|
|
72944
|
-
* de workflow; aquí estamos en runtime de navegador, performance.now es seguro.
|
|
72945
|
-
*/
|
|
72946
|
-
emitTypingDebounced() {
|
|
72947
|
-
const now = performance.now();
|
|
72948
|
-
if (now - this.lastTypingEmit > 2000) {
|
|
72949
|
-
this.lastTypingEmit = now;
|
|
72950
|
-
this.typing.emit();
|
|
72951
|
-
}
|
|
72952
|
-
}
|
|
72953
|
-
// ─── Grabación de audio ───────────────────────────────────────────────────
|
|
72954
|
-
/** Selecciona un mimeType de audio soportado por el navegador. */
|
|
72955
|
-
pickAudioMime() {
|
|
72956
|
-
const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
|
|
72957
|
-
const MR = window.MediaRecorder;
|
|
72958
|
-
if (!MR?.isTypeSupported)
|
|
72959
|
-
return '';
|
|
72960
|
-
return candidates.find(c => MR.isTypeSupported(c)) ?? '';
|
|
72961
|
-
}
|
|
72962
|
-
async startRecording() {
|
|
72963
|
-
if (this.recording())
|
|
72964
|
-
return;
|
|
72965
|
-
if (!navigator.mediaDevices?.getUserMedia || !('MediaRecorder' in window)) {
|
|
72966
|
-
this.voice.emit(); // sin soporte: deja que el consumer decida (fallback)
|
|
72967
|
-
return;
|
|
72968
|
-
}
|
|
72969
|
-
try {
|
|
72970
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
72971
|
-
this.recStream = stream;
|
|
72972
|
-
const mimeType = this.pickAudioMime();
|
|
72973
|
-
this.recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
|
|
72974
|
-
this.recChunks = [];
|
|
72975
|
-
this.recorder.ondataavailable = e => {
|
|
72976
|
-
if (e.data.size > 0)
|
|
72977
|
-
this.recChunks.push(e.data);
|
|
72978
|
-
};
|
|
72979
|
-
this.recorder.start();
|
|
72980
|
-
this.recording.set(true);
|
|
72981
|
-
this.recSeconds.set(0);
|
|
72982
|
-
this.recTimer = setInterval(() => this.recSeconds.update(s => s + 1), 1000);
|
|
72983
|
-
}
|
|
72984
|
-
catch {
|
|
72985
|
-
this.cleanupRecording();
|
|
72986
|
-
}
|
|
72987
|
-
}
|
|
72988
|
-
stopAndSend() {
|
|
72989
|
-
const rec = this.recorder;
|
|
72990
|
-
if (!rec) {
|
|
72991
|
-
this.cleanupRecording();
|
|
72992
|
-
return;
|
|
72993
|
-
}
|
|
72994
|
-
rec.onstop = () => {
|
|
72995
|
-
const type = rec.mimeType || 'audio/webm';
|
|
72996
|
-
const blob = new Blob(this.recChunks, { type });
|
|
72997
|
-
const ext = type.includes('mp4') ? 'm4a' : 'webm';
|
|
72998
|
-
const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
|
|
72999
|
-
if (blob.size > 0)
|
|
73000
|
-
this.attach.emit(file);
|
|
73001
|
-
this.cleanupRecording();
|
|
73002
|
-
};
|
|
73003
|
-
rec.stop();
|
|
73004
|
-
}
|
|
73005
|
-
cancelRecording() {
|
|
73006
|
-
const rec = this.recorder;
|
|
73007
|
-
if (rec)
|
|
73008
|
-
rec.onstop = () => this.cleanupRecording();
|
|
73009
|
-
try {
|
|
73010
|
-
rec?.stop();
|
|
73011
|
-
}
|
|
73012
|
-
catch {
|
|
73013
|
-
this.cleanupRecording();
|
|
73014
|
-
}
|
|
73015
|
-
if (!rec)
|
|
73016
|
-
this.cleanupRecording();
|
|
73017
|
-
}
|
|
73018
|
-
cleanupRecording() {
|
|
73019
|
-
if (this.recTimer)
|
|
73020
|
-
clearInterval(this.recTimer);
|
|
73021
|
-
this.recTimer = undefined;
|
|
73022
|
-
this.recStream?.getTracks().forEach(t => t.stop());
|
|
73023
|
-
this.recStream = undefined;
|
|
73024
|
-
this.recorder = undefined;
|
|
73025
|
-
this.recChunks = [];
|
|
73026
|
-
this.recording.set(false);
|
|
73027
|
-
this.recSeconds.set(0);
|
|
73028
|
-
}
|
|
73029
|
-
ngOnDestroy() {
|
|
73030
|
-
this.cleanupRecording();
|
|
73031
|
-
}
|
|
73032
|
-
t(key) {
|
|
73033
|
-
return this.i18n.t(key, 'ChatComposer');
|
|
73034
|
-
}
|
|
73035
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73036
|
-
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: `
|
|
73037
|
-
<div class="composer">
|
|
73038
|
-
@if (replyingTo()) {
|
|
73039
|
-
<div class="reply-bar">
|
|
73040
|
-
<div class="reply-content">
|
|
73041
|
-
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
73042
|
-
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
73043
|
-
</div>
|
|
73044
|
-
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
73045
|
-
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73046
|
-
</button>
|
|
73047
|
-
</div>
|
|
73048
|
-
}
|
|
73049
|
-
|
|
73050
|
-
@if (emojiOpen()) {
|
|
73051
|
-
<div class="emoji-row">
|
|
73052
|
-
@for (e of quickEmojis; track e) {
|
|
73053
|
-
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
73054
|
-
}
|
|
73055
|
-
</div>
|
|
73056
|
-
}
|
|
73057
|
-
|
|
73058
|
-
@if (recording()) {
|
|
73059
|
-
<div class="rec-bar">
|
|
73060
|
-
<button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
|
|
73061
|
-
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
73062
|
-
</button>
|
|
73063
|
-
<span class="rec-dot"></span>
|
|
73064
|
-
<span class="rec-time">{{ recTimeLabel() }}</span>
|
|
73065
|
-
<span class="rec-spacer"></span>
|
|
73066
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="stopAndSend()">
|
|
73067
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73068
|
-
</button>
|
|
73069
|
-
</div>
|
|
73070
|
-
} @else {
|
|
73071
|
-
<div class="pill">
|
|
73072
|
-
@if (showAttach()) {
|
|
73073
|
-
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
73074
|
-
<ion-icon name="add-outline" aria-hidden="true" />
|
|
73075
|
-
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73076
|
-
</label>
|
|
73077
|
-
}
|
|
73078
|
-
|
|
73079
|
-
<ion-textarea
|
|
73080
|
-
class="field"
|
|
73081
|
-
[placeholder]="placeholderText()"
|
|
73082
|
-
[disabled]="disabled()"
|
|
73083
|
-
[maxlength]="maxLength()"
|
|
73084
|
-
[autoGrow]="true"
|
|
73085
|
-
[rows]="1"
|
|
73086
|
-
[value]="body()"
|
|
73087
|
-
[spellcheck]="false"
|
|
73088
|
-
autocapitalize="sentences"
|
|
73089
|
-
autocorrect="off"
|
|
73090
|
-
enterkeyhint="enter"
|
|
73091
|
-
(ionInput)="onInput($event)"
|
|
73092
|
-
(keydown)="onKeydown($event)"
|
|
73093
|
-
(paste)="onPaste($event)"
|
|
73094
|
-
></ion-textarea>
|
|
73095
|
-
|
|
73096
|
-
@if (showEmoji()) {
|
|
73097
|
-
<button
|
|
73098
|
-
class="icon-btn"
|
|
73099
|
-
[class.active]="emojiOpen()"
|
|
73100
|
-
[attr.aria-label]="t('emoji')"
|
|
73101
|
-
(click)="toggleEmoji()"
|
|
73102
|
-
>
|
|
73103
|
-
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
73104
|
-
</button>
|
|
73105
|
-
}
|
|
73106
|
-
|
|
73107
|
-
@if (canSend()) {
|
|
73108
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73109
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73110
|
-
</button>
|
|
73111
|
-
} @else if (showMic()) {
|
|
73112
|
-
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
|
|
73113
|
-
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73114
|
-
</button>
|
|
73115
|
-
}
|
|
73116
|
-
</div>
|
|
73117
|
-
}
|
|
73118
|
-
</div>
|
|
73119
|
-
`, 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)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
73120
|
-
}
|
|
73121
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
|
|
73122
|
-
type: Component,
|
|
73123
|
-
args: [{ selector: 'val-chat-composer', standalone: true, imports: [CommonModule, IonTextarea, IonIcon], template: `
|
|
73124
|
-
<div class="composer">
|
|
73125
|
-
@if (replyingTo()) {
|
|
73126
|
-
<div class="reply-bar">
|
|
73127
|
-
<div class="reply-content">
|
|
73128
|
-
<span class="reply-name">{{ replyingTo()!.senderName }}</span>
|
|
73129
|
-
<span class="reply-body">{{ replyingTo()!.body }}</span>
|
|
73130
|
-
</div>
|
|
73131
|
-
<button class="icon-btn" [attr.aria-label]="t('cancel')" (click)="cancelReply.emit()">
|
|
73132
|
-
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73133
|
-
</button>
|
|
73134
|
-
</div>
|
|
73135
|
-
}
|
|
73136
|
-
|
|
73137
|
-
@if (emojiOpen()) {
|
|
73138
|
-
<div class="emoji-row">
|
|
73139
|
-
@for (e of quickEmojis; track e) {
|
|
73140
|
-
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
73141
|
-
}
|
|
73142
|
-
</div>
|
|
73143
|
-
}
|
|
73144
|
-
|
|
73145
|
-
@if (recording()) {
|
|
73146
|
-
<div class="rec-bar">
|
|
73147
|
-
<button class="icon-btn danger" [attr.aria-label]="t('cancel')" (click)="cancelRecording()">
|
|
73148
|
-
<ion-icon name="trash-outline" aria-hidden="true" />
|
|
73149
|
-
</button>
|
|
73150
|
-
<span class="rec-dot"></span>
|
|
73151
|
-
<span class="rec-time">{{ recTimeLabel() }}</span>
|
|
73152
|
-
<span class="rec-spacer"></span>
|
|
73153
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="stopAndSend()">
|
|
73154
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73155
|
-
</button>
|
|
73156
|
-
</div>
|
|
73157
|
-
} @else {
|
|
73158
|
-
<div class="pill">
|
|
73159
|
-
@if (showAttach()) {
|
|
73160
|
-
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
73161
|
-
<ion-icon name="add-outline" aria-hidden="true" />
|
|
73162
|
-
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73163
|
-
</label>
|
|
73164
|
-
}
|
|
73165
|
-
|
|
73166
|
-
<ion-textarea
|
|
73167
|
-
class="field"
|
|
73168
|
-
[placeholder]="placeholderText()"
|
|
73169
|
-
[disabled]="disabled()"
|
|
73170
|
-
[maxlength]="maxLength()"
|
|
73171
|
-
[autoGrow]="true"
|
|
73172
|
-
[rows]="1"
|
|
73173
|
-
[value]="body()"
|
|
73174
|
-
[spellcheck]="false"
|
|
73175
|
-
autocapitalize="sentences"
|
|
73176
|
-
autocorrect="off"
|
|
73177
|
-
enterkeyhint="enter"
|
|
73178
|
-
(ionInput)="onInput($event)"
|
|
73179
|
-
(keydown)="onKeydown($event)"
|
|
73180
|
-
(paste)="onPaste($event)"
|
|
73181
|
-
></ion-textarea>
|
|
73182
|
-
|
|
73183
|
-
@if (showEmoji()) {
|
|
73184
|
-
<button
|
|
73185
|
-
class="icon-btn"
|
|
73186
|
-
[class.active]="emojiOpen()"
|
|
73187
|
-
[attr.aria-label]="t('emoji')"
|
|
73188
|
-
(click)="toggleEmoji()"
|
|
73189
|
-
>
|
|
73190
|
-
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
73191
|
-
</button>
|
|
73192
|
-
}
|
|
73193
|
-
|
|
73194
|
-
@if (canSend()) {
|
|
73195
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73196
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73197
|
-
</button>
|
|
73198
|
-
} @else if (showMic()) {
|
|
73199
|
-
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
|
|
73200
|
-
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73201
|
-
</button>
|
|
73202
|
-
}
|
|
73203
|
-
</div>
|
|
73204
|
-
}
|
|
73205
|
-
</div>
|
|
73206
|
-
`, 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)}.icon-btn.danger{color:var(--ion-color-danger, #eb445a)}.rec-bar{display:flex;align-items:center;gap:10px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.rec-dot{width:10px;height:10px;border-radius:50%;background:var(--ion-color-danger, #eb445a);animation:rec-pulse 1.2s ease-in-out infinite}.rec-time{font-variant-numeric:tabular-nums;font-size:.9375rem;color:var(--ion-text-color, #000)}.rec-spacer{flex:1}@keyframes rec-pulse{0%,to{opacity:1}50%{opacity:.3}}\n"] }]
|
|
73207
|
-
}], ctorParameters: () => [] });
|
|
73208
|
-
|
|
73209
|
-
/**
|
|
73210
|
-
* val-conversation-list-item — fila de lista de conversaciones estilo
|
|
73211
|
-
* Telegram/WhatsApp: avatar (imagen o iniciales), nombre + hora relativa,
|
|
73212
|
-
* preview del último mensaje + badge de no-leídos. Signal inputs, dark-safe.
|
|
73213
|
-
*
|
|
73214
|
-
* @example
|
|
73215
|
-
* <val-conversation-list-item
|
|
73216
|
-
* [name]="conv.name"
|
|
73217
|
-
* [lastMessage]="conv.lastMsgPreview"
|
|
73218
|
-
* [timestamp]="conv.lastMsgAt"
|
|
73219
|
-
* [unreadCount]="conv.unread"
|
|
73220
|
-
* (select)="open(conv.convId)" />
|
|
73221
|
-
*/
|
|
73222
|
-
class ConversationListItemComponent {
|
|
73223
|
-
constructor() {
|
|
73224
|
-
this.i18n = inject(I18nService);
|
|
73225
|
-
this.name = input('');
|
|
73226
|
-
this.avatarUrl = input(undefined);
|
|
73227
|
-
this.lastMessage = input('');
|
|
73228
|
-
this.timestamp = input(undefined);
|
|
73229
|
-
this.unreadCount = input(0);
|
|
73230
|
-
this.locale = input('es-CL');
|
|
73231
|
-
this.select = output();
|
|
73232
|
-
this.initials = computed(() => {
|
|
73233
|
-
const name = this.name().trim();
|
|
73234
|
-
if (!name)
|
|
73235
|
-
return '?';
|
|
73236
|
-
const parts = name.split(/\s+/);
|
|
73237
|
-
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
73238
|
-
});
|
|
73239
|
-
this.timeLabel = computed(() => formatRelativeTime(this.timestamp(), this.locale(), {
|
|
73240
|
-
today: '',
|
|
73241
|
-
yesterday: this.i18n.t('yesterday', '_global'),
|
|
73242
|
-
}));
|
|
73243
|
-
this.i18n.registerDefaults('ConversationListItem', {
|
|
73244
|
-
es: { empty: 'Sin mensajes' },
|
|
73245
|
-
en: { empty: 'No messages' },
|
|
73246
|
-
});
|
|
73247
|
-
}
|
|
73248
|
-
emptyText() {
|
|
73249
|
-
this.i18n.lang();
|
|
73250
|
-
return this.i18n.t('empty', 'ConversationListItem');
|
|
73251
|
-
}
|
|
73252
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73253
|
-
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: `
|
|
73254
|
-
<button class="item" (click)="select.emit()">
|
|
73255
|
-
<div class="avatar">
|
|
73256
|
-
@if (avatarUrl()) {
|
|
73257
|
-
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73258
|
-
} @else {
|
|
73259
|
-
<span>{{ initials() }}</span>
|
|
73260
|
-
}
|
|
73261
|
-
</div>
|
|
73262
|
-
<div class="content">
|
|
73263
|
-
<div class="top">
|
|
73264
|
-
<span class="name">{{ name() }}</span>
|
|
73265
|
-
@if (timeLabel()) {
|
|
73266
|
-
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73267
|
-
}
|
|
73268
|
-
</div>
|
|
73269
|
-
<div class="bottom">
|
|
73270
|
-
<span class="preview" [class.muted]="!lastMessage()">
|
|
73271
|
-
{{ lastMessage() || emptyText() }}
|
|
73272
|
-
</span>
|
|
73273
|
-
@if (unreadCount() > 0) {
|
|
73274
|
-
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73275
|
-
}
|
|
73276
|
-
</div>
|
|
73277
|
-
</div>
|
|
73278
|
-
</button>
|
|
73279
|
-
`, 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 }] }); }
|
|
73280
|
-
}
|
|
73281
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConversationListItemComponent, decorators: [{
|
|
73282
|
-
type: Component,
|
|
73283
|
-
args: [{ selector: 'val-conversation-list-item', standalone: true, imports: [CommonModule], template: `
|
|
73284
|
-
<button class="item" (click)="select.emit()">
|
|
73285
|
-
<div class="avatar">
|
|
73286
|
-
@if (avatarUrl()) {
|
|
73287
|
-
<img [src]="avatarUrl()" [alt]="name()" />
|
|
73288
|
-
} @else {
|
|
73289
|
-
<span>{{ initials() }}</span>
|
|
73290
|
-
}
|
|
73291
|
-
</div>
|
|
73292
|
-
<div class="content">
|
|
73293
|
-
<div class="top">
|
|
73294
|
-
<span class="name">{{ name() }}</span>
|
|
73295
|
-
@if (timeLabel()) {
|
|
73296
|
-
<span class="time" [class.unread]="unreadCount() > 0">{{ timeLabel() }}</span>
|
|
73297
|
-
}
|
|
73298
|
-
</div>
|
|
73299
|
-
<div class="bottom">
|
|
73300
|
-
<span class="preview" [class.muted]="!lastMessage()">
|
|
73301
|
-
{{ lastMessage() || emptyText() }}
|
|
73302
|
-
</span>
|
|
73303
|
-
@if (unreadCount() > 0) {
|
|
73304
|
-
<span class="badge">{{ unreadCount() > 99 ? '99+' : unreadCount() }}</span>
|
|
73305
|
-
}
|
|
73306
|
-
</div>
|
|
73307
|
-
</div>
|
|
73308
|
-
</button>
|
|
73309
|
-
`, 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"] }]
|
|
73310
|
-
}], ctorParameters: () => [] });
|
|
73311
|
-
|
|
73312
72671
|
const TYPING_I18N = {
|
|
73313
72672
|
es: {
|
|
73314
72673
|
one: '{name} esta escribiendo',
|
|
@@ -73324,9 +72683,8 @@ const TYPING_I18N = {
|
|
|
73324
72683
|
/**
|
|
73325
72684
|
* val-typing-indicator
|
|
73326
72685
|
*
|
|
73327
|
-
* Muestra quienes estan escribiendo con animacion de tres puntos.
|
|
73328
|
-
* si
|
|
73329
|
-
* 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.
|
|
73330
72688
|
*
|
|
73331
72689
|
* @example
|
|
73332
72690
|
* <val-typing-indicator [typingUsers]="['Maria', 'Carlos']" />
|
|
@@ -73334,10 +72692,10 @@ const TYPING_I18N = {
|
|
|
73334
72692
|
class TypingIndicatorComponent {
|
|
73335
72693
|
constructor() {
|
|
73336
72694
|
this.i18n = inject(I18nService);
|
|
73337
|
-
this.typingUsers =
|
|
72695
|
+
this.typingUsers = [];
|
|
73338
72696
|
this.typingText = computed(() => {
|
|
73339
72697
|
this.i18n.lang();
|
|
73340
|
-
const users = this.typingUsers
|
|
72698
|
+
const users = this.typingUsers ?? [];
|
|
73341
72699
|
if (users.length === 0)
|
|
73342
72700
|
return '';
|
|
73343
72701
|
if (users.length === 1) {
|
|
@@ -73351,8 +72709,8 @@ class TypingIndicatorComponent {
|
|
|
73351
72709
|
this.i18n.registerDefaults('TypingIndicator', TYPING_I18N);
|
|
73352
72710
|
}
|
|
73353
72711
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73354
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: TypingIndicatorComponent, isStandalone: true, selector: "val-typing-indicator", inputs: { typingUsers:
|
|
73355
|
-
@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) {
|
|
73356
72714
|
<div class="typing-indicator">
|
|
73357
72715
|
<div class="dots">
|
|
73358
72716
|
<span></span>
|
|
@@ -73367,7 +72725,7 @@ class TypingIndicatorComponent {
|
|
|
73367
72725
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TypingIndicatorComponent, decorators: [{
|
|
73368
72726
|
type: Component,
|
|
73369
72727
|
args: [{ selector: 'val-typing-indicator', standalone: true, imports: [CommonModule], template: `
|
|
73370
|
-
@if (typingUsers
|
|
72728
|
+
@if (typingUsers && typingUsers.length > 0) {
|
|
73371
72729
|
<div class="typing-indicator">
|
|
73372
72730
|
<div class="dots">
|
|
73373
72731
|
<span></span>
|
|
@@ -73378,280 +72736,270 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73378
72736
|
</div>
|
|
73379
72737
|
}
|
|
73380
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"] }]
|
|
73381
|
-
}], ctorParameters: () => []
|
|
72739
|
+
}], ctorParameters: () => [], propDecorators: { typingUsers: [{
|
|
72740
|
+
type: Input
|
|
72741
|
+
}] } });
|
|
73382
72742
|
|
|
73383
|
-
addIcons({
|
|
72743
|
+
addIcons({ chatbubblesOutline });
|
|
73384
72744
|
const CHAT_WINDOW_I18N = {
|
|
73385
72745
|
es: {
|
|
73386
72746
|
conversationClosed: 'Esta conversacion esta cerrada',
|
|
73387
72747
|
loading: 'Cargando mensajes...',
|
|
73388
|
-
noMessages: '
|
|
73389
|
-
newMessages: 'Mensajes nuevos',
|
|
72748
|
+
noMessages: 'No hay mensajes aun',
|
|
73390
72749
|
},
|
|
73391
72750
|
en: {
|
|
73392
72751
|
conversationClosed: 'This conversation is closed',
|
|
73393
72752
|
loading: 'Loading messages...',
|
|
73394
|
-
noMessages: 'No messages yet
|
|
73395
|
-
newMessages: 'New messages',
|
|
72753
|
+
noMessages: 'No messages yet',
|
|
73396
72754
|
},
|
|
73397
72755
|
};
|
|
73398
72756
|
/**
|
|
73399
|
-
* val-chat-window
|
|
72757
|
+
* val-chat-window
|
|
73400
72758
|
*
|
|
73401
|
-
*
|
|
73402
|
-
*
|
|
73403
|
-
*
|
|
73404
|
-
*
|
|
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.
|
|
73405
72763
|
*
|
|
73406
|
-
*
|
|
73407
|
-
*
|
|
73408
|
-
*
|
|
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
|
+
* />
|
|
73409
72774
|
*/
|
|
73410
72775
|
class ChatWindowComponent {
|
|
73411
72776
|
constructor() {
|
|
73412
72777
|
this.i18n = inject(I18nService);
|
|
73413
|
-
this.convId =
|
|
73414
|
-
this.messages =
|
|
73415
|
-
this.
|
|
73416
|
-
this.
|
|
73417
|
-
this.
|
|
73418
|
-
this.
|
|
73419
|
-
this.
|
|
73420
|
-
this.
|
|
73421
|
-
this.
|
|
73422
|
-
this.
|
|
73423
|
-
this.
|
|
73424
|
-
this.
|
|
73425
|
-
this.
|
|
73426
|
-
this.
|
|
73427
|
-
this.
|
|
73428
|
-
|
|
73429
|
-
this.replyingTo = signal(null);
|
|
73430
|
-
this.atBottom = signal(true);
|
|
73431
|
-
this.hasNew = signal(false);
|
|
73432
|
-
this.locale = computed(() => (this.i18n.lang() === 'en' ? 'en-US' : 'es-CL'));
|
|
73433
|
-
this.rows = computed(() => {
|
|
73434
|
-
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 ?? [];
|
|
73435
72794
|
if (msgs.length === 0)
|
|
73436
72795
|
return [];
|
|
73437
|
-
const
|
|
73438
|
-
|
|
73439
|
-
|
|
73440
|
-
|
|
73441
|
-
|
|
73442
|
-
|
|
73443
|
-
|
|
73444
|
-
|
|
73445
|
-
|
|
73446
|
-
|
|
73447
|
-
|
|
73448
|
-
|
|
73449
|
-
|
|
73450
|
-
|
|
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);
|
|
73451
72810
|
}
|
|
73452
|
-
const prev = msgs[i - 1];
|
|
73453
|
-
const next = msgs[i + 1];
|
|
73454
|
-
const firstInGroup = newDay || !prev || prev.senderId !== m.senderId;
|
|
73455
|
-
const nextDay = next
|
|
73456
|
-
? `${new Date(next.createdAt).getFullYear()}-${new Date(next.createdAt).getMonth()}-${new Date(next.createdAt).getDate()}`
|
|
73457
|
-
: '';
|
|
73458
|
-
const lastInGroup = !next || next.senderId !== m.senderId || nextDay !== day;
|
|
73459
|
-
out.push({
|
|
73460
|
-
kind: 'msg',
|
|
73461
|
-
id: m.msgId,
|
|
73462
|
-
msg: m,
|
|
73463
|
-
showName: firstInGroup && !m.isMine,
|
|
73464
|
-
showAvatar: lastInGroup && !m.isMine,
|
|
73465
|
-
tail: lastInGroup,
|
|
73466
|
-
});
|
|
73467
72811
|
}
|
|
73468
|
-
|
|
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');
|
|
73469
72824
|
});
|
|
73470
72825
|
this.i18n.registerDefaults('ChatWindow', CHAT_WINDOW_I18N);
|
|
73471
|
-
|
|
73472
|
-
|
|
73473
|
-
|
|
73474
|
-
|
|
73475
|
-
|
|
73476
|
-
|
|
73477
|
-
|
|
73478
|
-
|
|
73479
|
-
|
|
73480
|
-
|
|
73481
|
-
|
|
73482
|
-
|
|
73483
|
-
|
|
73484
|
-
|
|
73485
|
-
|
|
73486
|
-
|
|
73487
|
-
|
|
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;
|
|
73488
72845
|
}
|
|
73489
|
-
}
|
|
72846
|
+
}
|
|
73490
72847
|
}
|
|
73491
|
-
|
|
73492
|
-
|
|
73493
|
-
|
|
73494
|
-
|
|
73495
|
-
|
|
73496
|
-
this.atBottom.set(nearBottom);
|
|
73497
|
-
if (nearBottom)
|
|
73498
|
-
this.hasNew.set(false);
|
|
73499
|
-
if (el.scrollTop < 40)
|
|
73500
|
-
this.loadMore.emit();
|
|
72848
|
+
ngAfterViewChecked() {
|
|
72849
|
+
if (this.shouldScrollToBottom) {
|
|
72850
|
+
this.scrollToBottom();
|
|
72851
|
+
this.shouldScrollToBottom = false;
|
|
72852
|
+
}
|
|
73501
72853
|
}
|
|
73502
|
-
|
|
73503
|
-
const el = this.
|
|
73504
|
-
if (!el)
|
|
72854
|
+
adjustToKeyboard() {
|
|
72855
|
+
const el = this.chatWindowElRef?.nativeElement;
|
|
72856
|
+
if (!el || !window.visualViewport)
|
|
73505
72857
|
return;
|
|
73506
|
-
|
|
73507
|
-
|
|
73508
|
-
|
|
72858
|
+
const rect = el.getBoundingClientRect();
|
|
72859
|
+
const available = window.visualViewport.height - rect.top;
|
|
72860
|
+
if (available > 50) {
|
|
72861
|
+
el.style.height = `${available}px`;
|
|
72862
|
+
}
|
|
73509
72863
|
}
|
|
73510
|
-
|
|
73511
|
-
this.
|
|
73512
|
-
this.replyingTo.set(null);
|
|
72864
|
+
onReactionClick(event) {
|
|
72865
|
+
this.reactionClick.emit({ msgId: event.msgId, token: event.token });
|
|
73513
72866
|
}
|
|
73514
|
-
|
|
73515
|
-
|
|
73516
|
-
|
|
73517
|
-
|
|
73518
|
-
|
|
73519
|
-
|
|
73520
|
-
|
|
73521
|
-
|
|
73522
|
-
|
|
73523
|
-
|
|
73524
|
-
case 'edit':
|
|
73525
|
-
this.editMessage.emit(event.msgId);
|
|
73526
|
-
break;
|
|
73527
|
-
case 'delete':
|
|
73528
|
-
this.deleteMessage.emit(event.msgId);
|
|
73529
|
-
break;
|
|
73530
|
-
case 'react':
|
|
73531
|
-
this.reactionClick.emit({ msgId: event.msgId, token: event.token ?? '👍' });
|
|
73532
|
-
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;
|
|
73533
72877
|
}
|
|
73534
72878
|
}
|
|
73535
|
-
|
|
73536
|
-
|
|
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' });
|
|
73537
72893
|
}
|
|
73538
72894
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73539
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatWindowComponent, isStandalone: true, selector: "val-chat-window", inputs: { convId:
|
|
73540
|
-
<div class="chat">
|
|
73541
|
-
<div
|
|
73542
|
-
@if (isLoading
|
|
73543
|
-
<div class="state">
|
|
73544
|
-
<
|
|
73545
|
-
|
|
73546
|
-
} @else if (rows().length === 0) {
|
|
73547
|
-
<div class="state empty">
|
|
73548
|
-
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73549
|
-
<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>
|
|
73550
72902
|
</div>
|
|
73551
72903
|
} @else {
|
|
73552
|
-
@for (
|
|
73553
|
-
|
|
73554
|
-
<div class="date-
|
|
73555
|
-
|
|
73556
|
-
|
|
73557
|
-
|
|
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) {
|
|
73558
72911
|
<val-message-bubble
|
|
73559
|
-
[
|
|
73560
|
-
|
|
73561
|
-
|
|
73562
|
-
|
|
73563
|
-
|
|
73564
|
-
(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)"
|
|
73565
72917
|
/>
|
|
73566
72918
|
}
|
|
73567
72919
|
}
|
|
73568
72920
|
}
|
|
73569
|
-
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
73570
72921
|
</div>
|
|
73571
72922
|
|
|
73572
|
-
|
|
73573
|
-
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73574
|
-
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73575
|
-
@if (hasNew()) {
|
|
73576
|
-
<span class="dot"></span>
|
|
73577
|
-
}
|
|
73578
|
-
</button>
|
|
73579
|
-
}
|
|
72923
|
+
<val-typing-indicator [typingUsers]="typingUsers" />
|
|
73580
72924
|
|
|
73581
|
-
@if (isOpen
|
|
73582
|
-
<
|
|
73583
|
-
[replyingTo]="replyingTo()"
|
|
73584
|
-
(send)="onSend($event)"
|
|
73585
|
-
(typing)="typing.emit()"
|
|
73586
|
-
(attach)="attach.emit($event)"
|
|
73587
|
-
(voice)="voice.emit()"
|
|
73588
|
-
(cancelReply)="replyingTo.set(null)"
|
|
73589
|
-
/>
|
|
72925
|
+
@if (!isOpen) {
|
|
72926
|
+
<div class="closed-banner">{{ closedText() }}</div>
|
|
73590
72927
|
} @else {
|
|
73591
|
-
<
|
|
72928
|
+
<val-chat-input [props]="{ disabled: isLoading }" (send)="sendMessage.emit($event)" (typing)="onTyping()" />
|
|
73592
72929
|
}
|
|
73593
72930
|
</div>
|
|
73594
|
-
`, 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"] }] }); }
|
|
73595
72932
|
}
|
|
73596
72933
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
|
|
73597
72934
|
type: Component,
|
|
73598
|
-
args: [{ selector: 'val-chat-window', standalone: true, imports: [CommonModule, IonIcon, MessageBubbleComponent,
|
|
73599
|
-
<div class="chat">
|
|
73600
|
-
<div
|
|
73601
|
-
@if (isLoading
|
|
73602
|
-
<div class="state">
|
|
73603
|
-
<
|
|
73604
|
-
|
|
73605
|
-
} @else if (rows().length === 0) {
|
|
73606
|
-
<div class="state empty">
|
|
73607
|
-
<ion-icon name="chatbubbles-outline" aria-hidden="true" />
|
|
73608
|
-
<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>
|
|
73609
72942
|
</div>
|
|
73610
72943
|
} @else {
|
|
73611
|
-
@for (
|
|
73612
|
-
|
|
73613
|
-
<div class="date-
|
|
73614
|
-
|
|
73615
|
-
|
|
73616
|
-
|
|
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) {
|
|
73617
72951
|
<val-message-bubble
|
|
73618
|
-
[
|
|
73619
|
-
|
|
73620
|
-
|
|
73621
|
-
|
|
73622
|
-
|
|
73623
|
-
(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)"
|
|
73624
72957
|
/>
|
|
73625
72958
|
}
|
|
73626
72959
|
}
|
|
73627
72960
|
}
|
|
73628
|
-
<val-typing-indicator [typingUsers]="typingUsers()" />
|
|
73629
72961
|
</div>
|
|
73630
72962
|
|
|
73631
|
-
|
|
73632
|
-
<button class="scroll-down" (click)="scrollToBottom(true)" aria-label="scroll">
|
|
73633
|
-
<ion-icon name="arrow-down-outline" aria-hidden="true" />
|
|
73634
|
-
@if (hasNew()) {
|
|
73635
|
-
<span class="dot"></span>
|
|
73636
|
-
}
|
|
73637
|
-
</button>
|
|
73638
|
-
}
|
|
72963
|
+
<val-typing-indicator [typingUsers]="typingUsers" />
|
|
73639
72964
|
|
|
73640
|
-
@if (isOpen
|
|
73641
|
-
<
|
|
73642
|
-
[replyingTo]="replyingTo()"
|
|
73643
|
-
(send)="onSend($event)"
|
|
73644
|
-
(typing)="typing.emit()"
|
|
73645
|
-
(attach)="attach.emit($event)"
|
|
73646
|
-
(voice)="voice.emit()"
|
|
73647
|
-
(cancelReply)="replyingTo.set(null)"
|
|
73648
|
-
/>
|
|
72965
|
+
@if (!isOpen) {
|
|
72966
|
+
<div class="closed-banner">{{ closedText() }}</div>
|
|
73649
72967
|
} @else {
|
|
73650
|
-
<
|
|
72968
|
+
<val-chat-input [props]="{ disabled: isLoading }" (send)="sendMessage.emit($event)" (typing)="onTyping()" />
|
|
73651
72969
|
}
|
|
73652
72970
|
</div>
|
|
73653
|
-
`, styles: ["
|
|
73654
|
-
}], 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
|
+
}] } });
|
|
73655
73003
|
|
|
73656
73004
|
const THREAD_PANEL_I18N = {
|
|
73657
73005
|
es: {
|
|
@@ -73694,7 +73042,7 @@ class ThreadPanelComponent {
|
|
|
73694
73042
|
this.conv = signal(null);
|
|
73695
73043
|
this.messages = signal([]);
|
|
73696
73044
|
this.typingUsers = signal([]);
|
|
73697
|
-
this.isConvOpen = computed(() => this.conv()?.
|
|
73045
|
+
this.isConvOpen = computed(() => this.conv()?.status === 'open');
|
|
73698
73046
|
this.loadingText = computed(() => {
|
|
73699
73047
|
this.i18n.lang();
|
|
73700
73048
|
return this.i18n.t('loading', 'ThreadPanel');
|
|
@@ -73732,10 +73080,10 @@ class ThreadPanelComponent {
|
|
|
73732
73080
|
const appId = this.chatConfig?.appId ?? '';
|
|
73733
73081
|
if (orgId && appId) {
|
|
73734
73082
|
this.msgSub = this.conversationSvc
|
|
73735
|
-
.watchMessages(conversation.
|
|
73083
|
+
.watchMessages(conversation.id, orgId, appId)
|
|
73736
73084
|
.subscribe(msgs => this.messages.set(msgs));
|
|
73737
73085
|
this.typingSub = this.conversationSvc
|
|
73738
|
-
.watchTyping(conversation.
|
|
73086
|
+
.watchTyping(conversation.id, orgId, appId)
|
|
73739
73087
|
.subscribe(users => this.typingUsers.set(users));
|
|
73740
73088
|
}
|
|
73741
73089
|
}
|
|
@@ -73753,14 +73101,14 @@ class ThreadPanelComponent {
|
|
|
73753
73101
|
const orgId = this.getOrgId();
|
|
73754
73102
|
const appId = this.chatConfig?.appId ?? '';
|
|
73755
73103
|
if (conv && orgId && appId && this.conversationSvc) {
|
|
73756
|
-
this.conversationSvc.stopTyping(conv.
|
|
73104
|
+
this.conversationSvc.stopTyping(conv.id, orgId, appId);
|
|
73757
73105
|
}
|
|
73758
73106
|
}
|
|
73759
73107
|
onSend(body) {
|
|
73760
73108
|
const conv = this.conv();
|
|
73761
73109
|
if (!conv || !this.conversationSvc)
|
|
73762
73110
|
return;
|
|
73763
|
-
this.conversationSvc.sendMessage(conv.
|
|
73111
|
+
this.conversationSvc.sendMessage(conv.id, body).subscribe();
|
|
73764
73112
|
}
|
|
73765
73113
|
onReactionClick(event) {
|
|
73766
73114
|
// Reacciones se implementan a nivel de app consumer
|
|
@@ -73770,7 +73118,7 @@ class ThreadPanelComponent {
|
|
|
73770
73118
|
const conv = this.conv();
|
|
73771
73119
|
if (!conv || !this.conversationSvc)
|
|
73772
73120
|
return;
|
|
73773
|
-
this.conversationSvc.deleteMessage(conv.
|
|
73121
|
+
this.conversationSvc.deleteMessage(conv.id, msgId).subscribe();
|
|
73774
73122
|
}
|
|
73775
73123
|
onReplyTo(_msgId) {
|
|
73776
73124
|
// Implementacion futura: seleccionar mensaje de respuesta
|
|
@@ -73795,7 +73143,7 @@ class ThreadPanelComponent {
|
|
|
73795
73143
|
} @else {
|
|
73796
73144
|
<div class="thread-chat">
|
|
73797
73145
|
<val-chat-window
|
|
73798
|
-
[convId]="conv()?.
|
|
73146
|
+
[convId]="conv()?.id ?? ''"
|
|
73799
73147
|
[messages]="bubbleMessages()"
|
|
73800
73148
|
[currentUserId]="currentUserId"
|
|
73801
73149
|
[isOpen]="isConvOpen()"
|
|
@@ -73809,7 +73157,7 @@ class ThreadPanelComponent {
|
|
|
73809
73157
|
</div>
|
|
73810
73158
|
}
|
|
73811
73159
|
</div>
|
|
73812
|
-
`, 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"] }] }); }
|
|
73813
73161
|
}
|
|
73814
73162
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThreadPanelComponent, decorators: [{
|
|
73815
73163
|
type: Component,
|
|
@@ -73829,7 +73177,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73829
73177
|
} @else {
|
|
73830
73178
|
<div class="thread-chat">
|
|
73831
73179
|
<val-chat-window
|
|
73832
|
-
[convId]="conv()?.
|
|
73180
|
+
[convId]="conv()?.id ?? ''"
|
|
73833
73181
|
[messages]="bubbleMessages()"
|
|
73834
73182
|
[currentUserId]="currentUserId"
|
|
73835
73183
|
[isOpen]="isConvOpen()"
|
|
@@ -73863,5 +73211,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73863
73211
|
* Generated bundle index. Do not edit.
|
|
73864
73212
|
*/
|
|
73865
73213
|
|
|
73866
|
-
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 };
|
|
73867
73215
|
//# sourceMappingURL=valtech-components.mjs.map
|