valtech-components 4.0.229 → 4.0.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/chat-composer/chat-composer.component.mjs +104 -276
- package/esm2022/lib/components/molecules/message-bubble/message-bubble.component.mjs +4 -19
- package/esm2022/lib/components/organisms/chat-window/chat-window.component.mjs +2 -2
- package/esm2022/lib/services/chat/conversation.service.mjs +4 -15
- package/esm2022/lib/services/pdf/pdf.service.mjs +44 -4
- package/esm2022/lib/services/pdf/types.mjs +1 -1
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +150 -310
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/chat-composer/chat-composer.component.d.ts +13 -36
- package/lib/components/molecules/message-bubble/message-bubble.component.d.ts +0 -1
- package/lib/services/chat/conversation.service.d.ts +3 -5
- package/lib/services/pdf/pdf.service.d.ts +25 -3
- package/lib/services/pdf/types.d.ts +25 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
|
@@ -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.231';
|
|
60
60
|
|
|
61
61
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
62
62
|
let isRefreshing = false;
|
|
@@ -61830,12 +61830,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
61830
61830
|
}] } });
|
|
61831
61831
|
|
|
61832
61832
|
/**
|
|
61833
|
-
* Servicio
|
|
61833
|
+
* Servicio de PDF de plataforma (ADR-042). Reutilizable por cualquier app.
|
|
61834
61834
|
*
|
|
61835
61835
|
* El JWT del usuario se inyecta automáticamente por el authInterceptor
|
|
61836
61836
|
* (configurado vía provideValtechAuth). El app-id se toma de ValtechAuthConfig.
|
|
61837
61837
|
*
|
|
61838
|
-
*
|
|
61838
|
+
* Tres modos:
|
|
61839
|
+
* - generate() → HTTP sync: genera y devuelve la URL presignada.
|
|
61840
|
+
* - createJob() → async: encola un job (delivery.mode='async') y devuelve
|
|
61841
|
+
* { jobId, status:'queued' }. El PDF se genera fuera de banda.
|
|
61842
|
+
* - watchJob() → observa el estado del job en tiempo real (Firestore),
|
|
61843
|
+
* proyectado por el backend al completarse.
|
|
61844
|
+
*
|
|
61845
|
+
* Fuentes (en los 3 modos):
|
|
61839
61846
|
* - source.template → renderiza una plantilla desde DynamoDB
|
|
61840
61847
|
* - source.html → HTML arbitrario provisto por el caller
|
|
61841
61848
|
* - source.url → URL pública (Puppeteer la carga)
|
|
@@ -61844,6 +61851,8 @@ class PdfService {
|
|
|
61844
61851
|
constructor(config) {
|
|
61845
61852
|
this.config = config;
|
|
61846
61853
|
this.http = inject(HttpClient);
|
|
61854
|
+
this.auth = inject(AuthService);
|
|
61855
|
+
this.collections = inject(FirestoreCollectionFactory);
|
|
61847
61856
|
}
|
|
61848
61857
|
get baseUrl() {
|
|
61849
61858
|
return `${this.config?.apiUrl ?? ''}/v2/pdf`;
|
|
@@ -61860,6 +61869,35 @@ class PdfService {
|
|
|
61860
61869
|
generateAndDownload(req, filename = 'documento.pdf') {
|
|
61861
61870
|
return this.generate(req).pipe(switchMap$1(result => from(this._download(result.url, filename))));
|
|
61862
61871
|
}
|
|
61872
|
+
/**
|
|
61873
|
+
* Encola un job PDF async. El backend publica `pdf.generate.requested`; el PDF
|
|
61874
|
+
* Lambda lo genera fuera de banda y, al completar, proyecta el estado a
|
|
61875
|
+
* Firestore. Usar watchJob(jobId) para observar el resultado en tiempo real.
|
|
61876
|
+
*/
|
|
61877
|
+
createJob(req) {
|
|
61878
|
+
const body = {
|
|
61879
|
+
...req,
|
|
61880
|
+
delivery: { ...(req.delivery ?? {}), mode: 'async' },
|
|
61881
|
+
};
|
|
61882
|
+
return this.http.post(`${this.baseUrl}/generate`, body, {
|
|
61883
|
+
headers: this.appIdHeader,
|
|
61884
|
+
});
|
|
61885
|
+
}
|
|
61886
|
+
/**
|
|
61887
|
+
* Observa el estado de un job PDF async en tiempo real (onSnapshot del doc
|
|
61888
|
+
* `apps/{appId}/orgs/{orgId}/pdf-jobs/{jobId}`). Emite null si no hay org
|
|
61889
|
+
* activa o hasta que el doc exista. Cuando el job completa/falla, el doc trae
|
|
61890
|
+
* status + url. Cierra la suscripción al completar tu propia lógica.
|
|
61891
|
+
*/
|
|
61892
|
+
watchJob(jobId) {
|
|
61893
|
+
const orgId = this.auth.user()?.activeOrg ?? '';
|
|
61894
|
+
if (!orgId || !jobId)
|
|
61895
|
+
return of(null);
|
|
61896
|
+
const coll = this.collections.create(`orgs/${orgId}/pdf-jobs`, {
|
|
61897
|
+
timestamps: false,
|
|
61898
|
+
});
|
|
61899
|
+
return coll.watch(jobId);
|
|
61900
|
+
}
|
|
61863
61901
|
async _download(url, _filename) {
|
|
61864
61902
|
window.open(url, '_blank', 'noopener');
|
|
61865
61903
|
}
|
|
@@ -72171,21 +72209,10 @@ class ConversationService {
|
|
|
72171
72209
|
return this.http.delete(`${this.baseUrl}/conversations/${convId}/participants/${userId}`);
|
|
72172
72210
|
}
|
|
72173
72211
|
/**
|
|
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.
|
|
72212
|
+
* Envia un mensaje a una conversacion.
|
|
72177
72213
|
*/
|
|
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);
|
|
72214
|
+
sendMessage(convId, body) {
|
|
72215
|
+
return this.http.post(`${this.baseUrl}/conversations/${convId}/messages`, { body });
|
|
72189
72216
|
}
|
|
72190
72217
|
/**
|
|
72191
72218
|
* Elimina (marca como eliminado) un mensaje.
|
|
@@ -72412,8 +72439,7 @@ class MessageBubbleComponent {
|
|
|
72412
72439
|
return (parts[0][0] + (parts[1]?.[0] ?? '')).toUpperCase();
|
|
72413
72440
|
});
|
|
72414
72441
|
this.imageAttachments = computed(() => (this.msg().attachments ?? []).filter(a => a.mimeType?.startsWith('image/')));
|
|
72415
|
-
this.
|
|
72416
|
-
this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/') && !a.mimeType?.startsWith('audio/')));
|
|
72442
|
+
this.fileAttachments = computed(() => (this.msg().attachments ?? []).filter(a => !a.mimeType?.startsWith('image/')));
|
|
72417
72443
|
this.statusIcon = computed(() => {
|
|
72418
72444
|
switch (this.msg().status) {
|
|
72419
72445
|
case 'sending':
|
|
@@ -72484,13 +72510,6 @@ class MessageBubbleComponent {
|
|
|
72484
72510
|
}
|
|
72485
72511
|
</div>
|
|
72486
72512
|
}
|
|
72487
|
-
@if (audioAttachments().length > 0) {
|
|
72488
|
-
<div class="audios">
|
|
72489
|
-
@for (att of audioAttachments(); track att.url) {
|
|
72490
|
-
<audio class="att-audio" controls preload="metadata" [src]="att.url"></audio>
|
|
72491
|
-
}
|
|
72492
|
-
</div>
|
|
72493
|
-
}
|
|
72494
72513
|
@if (fileAttachments().length > 0) {
|
|
72495
72514
|
<div class="files">
|
|
72496
72515
|
@for (att of fileAttachments(); track att.url) {
|
|
@@ -72554,7 +72573,7 @@ class MessageBubbleComponent {
|
|
|
72554
72573
|
}
|
|
72555
72574
|
</div>
|
|
72556
72575
|
</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}.
|
|
72576
|
+
`, isInline: true, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
72558
72577
|
}
|
|
72559
72578
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessageBubbleComponent, decorators: [{
|
|
72560
72579
|
type: Component,
|
|
@@ -72599,13 +72618,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72599
72618
|
}
|
|
72600
72619
|
</div>
|
|
72601
72620
|
}
|
|
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
72621
|
@if (fileAttachments().length > 0) {
|
|
72610
72622
|
<div class="files">
|
|
72611
72623
|
@for (att of fileAttachments(); track att.url) {
|
|
@@ -72669,7 +72681,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72669
72681
|
}
|
|
72670
72682
|
</div>
|
|
72671
72683
|
</div>
|
|
72672
|
-
`, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.
|
|
72684
|
+
`, styles: [":host{display:block}.row{display:flex;align-items:flex-end;gap:8px;margin:1px 0}.row.mine{flex-direction:row-reverse}.row.tail{margin-bottom:8px}.avatar{width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:var(--ion-color-primary);color:#fff;font-size:.6875rem;font-weight:700}.avatar.hidden{visibility:hidden}.avatar img{width:100%;height:100%;object-fit:cover}.bubble-wrap{position:relative;max-width:min(78%,520px);display:flex;flex-direction:column}.row.mine .bubble-wrap{align-items:flex-end}.bubble{position:relative;padding:7px 11px;border-radius:16px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));color:var(--ion-text-color, #000);font-size:.9375rem;line-height:1.35;cursor:pointer;outline:none;word-break:break-word}.row:not(.mine).tail .bubble{border-bottom-left-radius:5px}.mine .bubble{background:var(--ion-color-primary);border-color:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff)}.mine.tail .bubble{border-bottom-right-radius:5px}.bubble.deleted{background:transparent;border-style:dashed;cursor:default}.name{display:block;font-size:.75rem;font-weight:700;color:var(--ion-color-primary);margin-bottom:2px}.reply-preview{display:flex;flex-direction:column;gap:1px;padding:4px 8px;margin-bottom:4px;border-left:3px solid currentColor;border-radius:6px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));font-size:.8125rem;opacity:.85}.reply-name{font-weight:700}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px}.body{white-space:pre-wrap}.deleted-text{font-style:italic;opacity:.6}.attachments{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.att-image{max-width:100%;border-radius:10px;display:block}.files{display:flex;flex-direction:column;gap:4px;margin-bottom:4px}.file-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:inherit;text-decoration:none;font-size:.8125rem}.meta{display:inline-flex;align-items:center;gap:4px;float:right;margin:2px 0 -2px 8px;font-size:.6875rem;opacity:.7}.edited{font-style:italic}.status{font-size:.875rem}.status.read{color:var(--ion-color-secondary, #4fc3f7);opacity:1}.status.failed{color:var(--ion-color-danger, #eb445a);opacity:1}.reactions{display:flex;flex-wrap:wrap;gap:4px;margin-top:3px}.reaction{display:inline-flex;align-items:center;gap:3px;padding:1px 7px;border-radius:999px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));font-size:.75rem;cursor:pointer;color:var(--ion-text-color, #000)}.reaction.active{border-color:var(--ion-color-primary);background:var(--ion-color-primary-tint, var(--ion-color-primary));color:var(--ion-color-primary-contrast, #fff)}.reaction .count{opacity:.75}.actions{position:absolute;top:-14px;display:flex;gap:2px;padding:2px;border-radius:999px;background:var(--ion-card-background, var(--ion-background-color, #fff));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));box-shadow:0 2px 8px #0000001f;opacity:0;pointer-events:none;transition:opacity .12s ease;z-index:2}.row.mine .actions{right:0}.row:not(.mine) .actions{left:0}.bubble-wrap:hover .actions,.actions.open{opacity:1;pointer-events:auto}.act{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1rem}.act:hover{background:var(--ion-color-step-100, rgba(127, 127, 127, .12));color:var(--ion-text-color, #000)}.act.danger:hover{color:var(--ion-color-danger, #eb445a)}\n"] }]
|
|
72673
72685
|
}], ctorParameters: () => [] });
|
|
72674
72686
|
|
|
72675
72687
|
addIcons({ sendOutline });
|
|
@@ -72822,35 +72834,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
72822
72834
|
type: Output
|
|
72823
72835
|
}] } });
|
|
72824
72836
|
|
|
72825
|
-
addIcons({ addOutline, closeOutline,
|
|
72837
|
+
addIcons({ addOutline, closeOutline, happyOutline, micOutline, arrowUpOutline });
|
|
72826
72838
|
const CHAT_COMPOSER_I18N = {
|
|
72827
72839
|
es: {
|
|
72828
72840
|
placeholder: 'Escribe un mensaje',
|
|
72829
72841
|
send: 'Enviar',
|
|
72830
72842
|
attach: 'Adjuntar',
|
|
72843
|
+
emoji: 'Emoji',
|
|
72831
72844
|
voice: 'Mensaje de voz',
|
|
72832
72845
|
replyingTo: 'Respondiendo a',
|
|
72833
72846
|
cancel: 'Cancelar',
|
|
72834
|
-
remove: 'Quitar',
|
|
72835
72847
|
},
|
|
72836
72848
|
en: {
|
|
72837
72849
|
placeholder: 'Write a message',
|
|
72838
72850
|
send: 'Send',
|
|
72839
72851
|
attach: 'Attach',
|
|
72852
|
+
emoji: 'Emoji',
|
|
72840
72853
|
voice: 'Voice message',
|
|
72841
72854
|
replyingTo: 'Replying to',
|
|
72842
72855
|
cancel: 'Cancel',
|
|
72843
|
-
remove: 'Remove',
|
|
72844
72856
|
},
|
|
72845
72857
|
};
|
|
72858
|
+
const QUICK_EMOJIS = ['👍', '❤️', '😂', '🎉', '🙏', '🔥', '😮', '😢'];
|
|
72846
72859
|
/**
|
|
72847
72860
|
* val-chat-composer — barra de redacción de chat moderna (signal inputs).
|
|
72848
72861
|
*
|
|
72849
|
-
* Pill con: adjuntar (+), textarea auto-grow, y mic/enviar
|
|
72850
|
-
*
|
|
72851
|
-
*
|
|
72852
|
-
*
|
|
72853
|
-
* No incluye picker de emoji: el teclado del sistema ya lo trae.
|
|
72862
|
+
* Pill con: adjuntar (+), textarea auto-grow, emoji (fila rápida) y mic/enviar
|
|
72863
|
+
* (mic cuando está vacío, enviar cuando hay texto — patrón WhatsApp/Telegram).
|
|
72864
|
+
* Muestra una barra de contexto "respondiendo a" cuando `replyingTo` está seteado.
|
|
72865
|
+
* `typing` se emite con debounce (máx. 1 vez cada 2s) para no spamear writes.
|
|
72854
72866
|
*/
|
|
72855
72867
|
class ChatComposerComponent {
|
|
72856
72868
|
constructor() {
|
|
@@ -72860,6 +72872,7 @@ class ChatComposerComponent {
|
|
|
72860
72872
|
this.maxLength = input(2000);
|
|
72861
72873
|
this.replyingTo = input(null);
|
|
72862
72874
|
this.showAttach = input(true);
|
|
72875
|
+
this.showEmoji = input(true);
|
|
72863
72876
|
this.showMic = input(true);
|
|
72864
72877
|
this.send = output();
|
|
72865
72878
|
this.typing = output();
|
|
@@ -72867,24 +72880,12 @@ class ChatComposerComponent {
|
|
|
72867
72880
|
this.voice = output();
|
|
72868
72881
|
this.cancelReply = output();
|
|
72869
72882
|
this.body = signal('');
|
|
72870
|
-
this.
|
|
72871
|
-
this.
|
|
72872
|
-
// Grabación de audio (MediaRecorder).
|
|
72873
|
-
this.recording = signal(false);
|
|
72874
|
-
this.recSeconds = signal(0);
|
|
72875
|
-
this.recChunks = [];
|
|
72876
|
-
this.recTimeLabel = computed(() => {
|
|
72877
|
-
const s = this.recSeconds();
|
|
72878
|
-
const mm = String(Math.floor(s / 60)).padStart(2, '0');
|
|
72879
|
-
const ss = String(s % 60).padStart(2, '0');
|
|
72880
|
-
return `${mm}:${ss}`;
|
|
72881
|
-
});
|
|
72883
|
+
this.emojiOpen = signal(false);
|
|
72884
|
+
this.quickEmojis = QUICK_EMOJIS;
|
|
72882
72885
|
this.lastTypingEmit = 0;
|
|
72883
72886
|
this.canSend = computed(() => {
|
|
72884
72887
|
const t = this.body().trim();
|
|
72885
|
-
|
|
72886
|
-
return false;
|
|
72887
|
-
return (t.length > 0 && t.length <= this.maxLength()) || this.pending().length > 0;
|
|
72888
|
+
return t.length > 0 && t.length <= this.maxLength() && !this.disabled();
|
|
72888
72889
|
});
|
|
72889
72890
|
this.placeholderText = computed(() => {
|
|
72890
72891
|
this.i18n.lang();
|
|
@@ -72904,61 +72905,29 @@ class ChatComposerComponent {
|
|
|
72904
72905
|
}
|
|
72905
72906
|
}
|
|
72906
72907
|
onSend() {
|
|
72907
|
-
if (!this.canSend())
|
|
72908
|
-
return;
|
|
72909
|
-
// Primero los adjuntos pendientes (cada uno se sube y manda en el consumer),
|
|
72910
|
-
// luego el texto como mensaje aparte si lo hay.
|
|
72911
|
-
const files = this.pending();
|
|
72912
|
-
for (const att of files) {
|
|
72913
|
-
this.attach.emit(att.file);
|
|
72914
|
-
if (att.url)
|
|
72915
|
-
URL.revokeObjectURL(att.url);
|
|
72916
|
-
}
|
|
72917
|
-
this.pending.set([]);
|
|
72918
72908
|
const trimmed = this.body().trim();
|
|
72919
|
-
if (trimmed
|
|
72920
|
-
|
|
72921
|
-
|
|
72909
|
+
if (!trimmed || trimmed.length > this.maxLength())
|
|
72910
|
+
return;
|
|
72911
|
+
this.send.emit(trimmed);
|
|
72922
72912
|
this.body.set('');
|
|
72913
|
+
this.emojiOpen.set(false);
|
|
72914
|
+
}
|
|
72915
|
+
toggleEmoji() {
|
|
72916
|
+
this.emojiOpen.update(v => !v);
|
|
72917
|
+
}
|
|
72918
|
+
insertEmoji(emoji) {
|
|
72919
|
+
this.body.update(v => v + emoji);
|
|
72923
72920
|
}
|
|
72924
72921
|
onFile(event) {
|
|
72925
72922
|
const input = event.target;
|
|
72926
72923
|
const file = input.files?.[0];
|
|
72927
72924
|
if (file)
|
|
72928
|
-
this.
|
|
72925
|
+
this.attach.emit(file);
|
|
72929
72926
|
input.value = '';
|
|
72930
72927
|
}
|
|
72931
|
-
/** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
|
|
72932
|
-
onPaste(event) {
|
|
72933
|
-
const items = event.clipboardData?.items;
|
|
72934
|
-
if (!items)
|
|
72935
|
-
return;
|
|
72936
|
-
for (const item of Array.from(items)) {
|
|
72937
|
-
if (item.type.startsWith('image/')) {
|
|
72938
|
-
const file = item.getAsFile();
|
|
72939
|
-
if (file) {
|
|
72940
|
-
event.preventDefault();
|
|
72941
|
-
this.addPending(file);
|
|
72942
|
-
}
|
|
72943
|
-
return;
|
|
72944
|
-
}
|
|
72945
|
-
}
|
|
72946
|
-
}
|
|
72947
|
-
addPending(file) {
|
|
72948
|
-
const isImage = file.type.startsWith('image/');
|
|
72949
|
-
const url = isImage ? URL.createObjectURL(file) : undefined;
|
|
72950
|
-
this.pending.update(arr => [...arr, { id: this.pendingId++, file, url, isImage }]);
|
|
72951
|
-
}
|
|
72952
|
-
removePending(id) {
|
|
72953
|
-
this.pending.update(arr => {
|
|
72954
|
-
const found = arr.find(a => a.id === id);
|
|
72955
|
-
if (found?.url)
|
|
72956
|
-
URL.revokeObjectURL(found.url);
|
|
72957
|
-
return arr.filter(a => a.id !== id);
|
|
72958
|
-
});
|
|
72959
|
-
}
|
|
72960
72928
|
/**
|
|
72961
|
-
* Emite typing como máximo 1 vez cada 2s.
|
|
72929
|
+
* Emite typing como máximo 1 vez cada 2s. No usa Date.now() directo en plantillas
|
|
72930
|
+
* de workflow; aquí estamos en runtime de navegador, performance.now es seguro.
|
|
72962
72931
|
*/
|
|
72963
72932
|
emitTypingDebounced() {
|
|
72964
72933
|
const now = performance.now();
|
|
@@ -72967,94 +72936,11 @@ class ChatComposerComponent {
|
|
|
72967
72936
|
this.typing.emit();
|
|
72968
72937
|
}
|
|
72969
72938
|
}
|
|
72970
|
-
// ─── Grabación de audio ───────────────────────────────────────────────────
|
|
72971
|
-
/** Selecciona un mimeType de audio soportado por el navegador. */
|
|
72972
|
-
pickAudioMime() {
|
|
72973
|
-
const candidates = ['audio/webm;codecs=opus', 'audio/webm', 'audio/mp4'];
|
|
72974
|
-
const MR = window.MediaRecorder;
|
|
72975
|
-
if (!MR?.isTypeSupported)
|
|
72976
|
-
return '';
|
|
72977
|
-
return candidates.find(c => MR.isTypeSupported(c)) ?? '';
|
|
72978
|
-
}
|
|
72979
|
-
async startRecording() {
|
|
72980
|
-
if (this.recording())
|
|
72981
|
-
return;
|
|
72982
|
-
if (!navigator.mediaDevices?.getUserMedia || !('MediaRecorder' in window)) {
|
|
72983
|
-
this.voice.emit(); // sin soporte: deja que el consumer decida (fallback)
|
|
72984
|
-
return;
|
|
72985
|
-
}
|
|
72986
|
-
try {
|
|
72987
|
-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
72988
|
-
this.recStream = stream;
|
|
72989
|
-
const mimeType = this.pickAudioMime();
|
|
72990
|
-
this.recorder = new MediaRecorder(stream, mimeType ? { mimeType } : undefined);
|
|
72991
|
-
this.recChunks = [];
|
|
72992
|
-
this.recorder.ondataavailable = e => {
|
|
72993
|
-
if (e.data.size > 0)
|
|
72994
|
-
this.recChunks.push(e.data);
|
|
72995
|
-
};
|
|
72996
|
-
this.recorder.start();
|
|
72997
|
-
this.recording.set(true);
|
|
72998
|
-
this.recSeconds.set(0);
|
|
72999
|
-
this.recTimer = setInterval(() => this.recSeconds.update(s => s + 1), 1000);
|
|
73000
|
-
}
|
|
73001
|
-
catch {
|
|
73002
|
-
this.cleanupRecording();
|
|
73003
|
-
}
|
|
73004
|
-
}
|
|
73005
|
-
stopAndSend() {
|
|
73006
|
-
const rec = this.recorder;
|
|
73007
|
-
if (!rec) {
|
|
73008
|
-
this.cleanupRecording();
|
|
73009
|
-
return;
|
|
73010
|
-
}
|
|
73011
|
-
rec.onstop = () => {
|
|
73012
|
-
const type = rec.mimeType || 'audio/webm';
|
|
73013
|
-
const blob = new Blob(this.recChunks, { type });
|
|
73014
|
-
const ext = type.includes('mp4') ? 'm4a' : 'webm';
|
|
73015
|
-
const file = new File([blob], `audio-${this.recSeconds()}s.${ext}`, { type });
|
|
73016
|
-
if (blob.size > 0)
|
|
73017
|
-
this.attach.emit(file);
|
|
73018
|
-
this.cleanupRecording();
|
|
73019
|
-
};
|
|
73020
|
-
rec.stop();
|
|
73021
|
-
}
|
|
73022
|
-
cancelRecording() {
|
|
73023
|
-
const rec = this.recorder;
|
|
73024
|
-
if (rec)
|
|
73025
|
-
rec.onstop = () => this.cleanupRecording();
|
|
73026
|
-
try {
|
|
73027
|
-
rec?.stop();
|
|
73028
|
-
}
|
|
73029
|
-
catch {
|
|
73030
|
-
this.cleanupRecording();
|
|
73031
|
-
}
|
|
73032
|
-
if (!rec)
|
|
73033
|
-
this.cleanupRecording();
|
|
73034
|
-
}
|
|
73035
|
-
cleanupRecording() {
|
|
73036
|
-
if (this.recTimer)
|
|
73037
|
-
clearInterval(this.recTimer);
|
|
73038
|
-
this.recTimer = undefined;
|
|
73039
|
-
this.recStream?.getTracks().forEach(t => t.stop());
|
|
73040
|
-
this.recStream = undefined;
|
|
73041
|
-
this.recorder = undefined;
|
|
73042
|
-
this.recChunks = [];
|
|
73043
|
-
this.recording.set(false);
|
|
73044
|
-
this.recSeconds.set(0);
|
|
73045
|
-
}
|
|
73046
|
-
ngOnDestroy() {
|
|
73047
|
-
this.cleanupRecording();
|
|
73048
|
-
for (const att of this.pending()) {
|
|
73049
|
-
if (att.url)
|
|
73050
|
-
URL.revokeObjectURL(att.url);
|
|
73051
|
-
}
|
|
73052
|
-
}
|
|
73053
72939
|
t(key) {
|
|
73054
72940
|
return this.i18n.t(key, 'ChatComposer');
|
|
73055
72941
|
}
|
|
73056
72942
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
73057
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChatComposerComponent, isStandalone: true, selector: "val-chat-composer", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, replyingTo: { classPropertyName: "replyingTo", publicName: "replyingTo", isSignal: true, isRequired: false, transformFunction: null }, showAttach: { classPropertyName: "showAttach", publicName: "showAttach", isSignal: true, isRequired: false, transformFunction: null }, showMic: { classPropertyName: "showMic", publicName: "showMic", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { send: "send", typing: "typing", attach: "attach", voice: "voice", cancelReply: "cancelReply" }, ngImport: i0, template: `
|
|
72943
|
+
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: `
|
|
73058
72944
|
<div class="composer">
|
|
73059
72945
|
@if (replyingTo()) {
|
|
73060
72946
|
<div class="reply-bar">
|
|
@@ -73068,75 +72954,52 @@ class ChatComposerComponent {
|
|
|
73068
72954
|
</div>
|
|
73069
72955
|
}
|
|
73070
72956
|
|
|
73071
|
-
@if (
|
|
73072
|
-
<div class="
|
|
73073
|
-
@for (
|
|
73074
|
-
<
|
|
73075
|
-
@if (att.isImage) {
|
|
73076
|
-
<img [src]="att.url" [alt]="att.file.name" />
|
|
73077
|
-
} @else {
|
|
73078
|
-
<ion-icon name="document-outline" aria-hidden="true" />
|
|
73079
|
-
<span class="thumb-name">{{ att.file.name }}</span>
|
|
73080
|
-
}
|
|
73081
|
-
<button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
|
|
73082
|
-
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73083
|
-
</button>
|
|
73084
|
-
</div>
|
|
72957
|
+
@if (emojiOpen()) {
|
|
72958
|
+
<div class="emoji-row">
|
|
72959
|
+
@for (e of quickEmojis; track e) {
|
|
72960
|
+
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
73085
72961
|
}
|
|
73086
72962
|
</div>
|
|
73087
72963
|
}
|
|
73088
72964
|
|
|
73089
|
-
|
|
73090
|
-
|
|
73091
|
-
<
|
|
73092
|
-
<ion-icon name="
|
|
72965
|
+
<div class="pill">
|
|
72966
|
+
@if (showAttach()) {
|
|
72967
|
+
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
72968
|
+
<ion-icon name="add-outline" aria-hidden="true" />
|
|
72969
|
+
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
72970
|
+
</label>
|
|
72971
|
+
}
|
|
72972
|
+
|
|
72973
|
+
<ion-textarea
|
|
72974
|
+
class="field"
|
|
72975
|
+
[placeholder]="placeholderText()"
|
|
72976
|
+
[disabled]="disabled()"
|
|
72977
|
+
[maxlength]="maxLength()"
|
|
72978
|
+
[autoGrow]="true"
|
|
72979
|
+
[rows]="1"
|
|
72980
|
+
[value]="body()"
|
|
72981
|
+
(ionInput)="onInput($event)"
|
|
72982
|
+
(keydown)="onKeydown($event)"
|
|
72983
|
+
></ion-textarea>
|
|
72984
|
+
|
|
72985
|
+
@if (showEmoji()) {
|
|
72986
|
+
<button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
|
|
72987
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
73093
72988
|
</button>
|
|
73094
|
-
|
|
73095
|
-
|
|
73096
|
-
|
|
73097
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="
|
|
72989
|
+
}
|
|
72990
|
+
|
|
72991
|
+
@if (canSend()) {
|
|
72992
|
+
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73098
72993
|
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73099
72994
|
</button>
|
|
73100
|
-
|
|
73101
|
-
|
|
73102
|
-
|
|
73103
|
-
|
|
73104
|
-
|
|
73105
|
-
|
|
73106
|
-
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73107
|
-
</label>
|
|
73108
|
-
}
|
|
73109
|
-
|
|
73110
|
-
<ion-textarea
|
|
73111
|
-
class="field"
|
|
73112
|
-
[placeholder]="placeholderText()"
|
|
73113
|
-
[disabled]="disabled()"
|
|
73114
|
-
[maxlength]="maxLength()"
|
|
73115
|
-
[autoGrow]="true"
|
|
73116
|
-
[rows]="1"
|
|
73117
|
-
[value]="body()"
|
|
73118
|
-
[spellcheck]="false"
|
|
73119
|
-
autocapitalize="sentences"
|
|
73120
|
-
autocorrect="off"
|
|
73121
|
-
enterkeyhint="enter"
|
|
73122
|
-
(ionInput)="onInput($event)"
|
|
73123
|
-
(keydown)="onKeydown($event)"
|
|
73124
|
-
(paste)="onPaste($event)"
|
|
73125
|
-
></ion-textarea>
|
|
73126
|
-
|
|
73127
|
-
@if (canSend()) {
|
|
73128
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73129
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73130
|
-
</button>
|
|
73131
|
-
} @else if (showMic()) {
|
|
73132
|
-
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
|
|
73133
|
-
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73134
|
-
</button>
|
|
73135
|
-
}
|
|
73136
|
-
</div>
|
|
73137
|
-
}
|
|
72995
|
+
} @else if (showMic()) {
|
|
72996
|
+
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
|
|
72997
|
+
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
72998
|
+
</button>
|
|
72999
|
+
}
|
|
73000
|
+
</div>
|
|
73138
73001
|
</div>
|
|
73139
|
-
`, 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)}.
|
|
73002
|
+
`, isInline: true, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
|
|
73140
73003
|
}
|
|
73141
73004
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatComposerComponent, decorators: [{
|
|
73142
73005
|
type: Component,
|
|
@@ -73154,75 +73017,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
73154
73017
|
</div>
|
|
73155
73018
|
}
|
|
73156
73019
|
|
|
73157
|
-
@if (
|
|
73158
|
-
<div class="
|
|
73159
|
-
@for (
|
|
73160
|
-
<
|
|
73161
|
-
@if (att.isImage) {
|
|
73162
|
-
<img [src]="att.url" [alt]="att.file.name" />
|
|
73163
|
-
} @else {
|
|
73164
|
-
<ion-icon name="document-outline" aria-hidden="true" />
|
|
73165
|
-
<span class="thumb-name">{{ att.file.name }}</span>
|
|
73166
|
-
}
|
|
73167
|
-
<button class="thumb-remove" [attr.aria-label]="t('remove')" (click)="removePending(att.id)">
|
|
73168
|
-
<ion-icon name="close-outline" aria-hidden="true" />
|
|
73169
|
-
</button>
|
|
73170
|
-
</div>
|
|
73020
|
+
@if (emojiOpen()) {
|
|
73021
|
+
<div class="emoji-row">
|
|
73022
|
+
@for (e of quickEmojis; track e) {
|
|
73023
|
+
<button class="emoji" (click)="insertEmoji(e)">{{ e }}</button>
|
|
73171
73024
|
}
|
|
73172
73025
|
</div>
|
|
73173
73026
|
}
|
|
73174
73027
|
|
|
73175
|
-
|
|
73176
|
-
|
|
73177
|
-
<
|
|
73178
|
-
<ion-icon name="
|
|
73028
|
+
<div class="pill">
|
|
73029
|
+
@if (showAttach()) {
|
|
73030
|
+
<label class="icon-btn" [attr.aria-label]="t('attach')">
|
|
73031
|
+
<ion-icon name="add-outline" aria-hidden="true" />
|
|
73032
|
+
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73033
|
+
</label>
|
|
73034
|
+
}
|
|
73035
|
+
|
|
73036
|
+
<ion-textarea
|
|
73037
|
+
class="field"
|
|
73038
|
+
[placeholder]="placeholderText()"
|
|
73039
|
+
[disabled]="disabled()"
|
|
73040
|
+
[maxlength]="maxLength()"
|
|
73041
|
+
[autoGrow]="true"
|
|
73042
|
+
[rows]="1"
|
|
73043
|
+
[value]="body()"
|
|
73044
|
+
(ionInput)="onInput($event)"
|
|
73045
|
+
(keydown)="onKeydown($event)"
|
|
73046
|
+
></ion-textarea>
|
|
73047
|
+
|
|
73048
|
+
@if (showEmoji()) {
|
|
73049
|
+
<button class="icon-btn" [class.active]="emojiOpen()" [attr.aria-label]="t('emoji')" (click)="toggleEmoji()">
|
|
73050
|
+
<ion-icon name="happy-outline" aria-hidden="true" />
|
|
73179
73051
|
</button>
|
|
73180
|
-
|
|
73181
|
-
|
|
73182
|
-
|
|
73183
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="
|
|
73052
|
+
}
|
|
73053
|
+
|
|
73054
|
+
@if (canSend()) {
|
|
73055
|
+
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73184
73056
|
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73185
73057
|
</button>
|
|
73186
|
-
|
|
73187
|
-
|
|
73188
|
-
|
|
73189
|
-
|
|
73190
|
-
|
|
73191
|
-
|
|
73192
|
-
<input type="file" hidden (change)="onFile($event)" accept="image/*,application/pdf" />
|
|
73193
|
-
</label>
|
|
73194
|
-
}
|
|
73195
|
-
|
|
73196
|
-
<ion-textarea
|
|
73197
|
-
class="field"
|
|
73198
|
-
[placeholder]="placeholderText()"
|
|
73199
|
-
[disabled]="disabled()"
|
|
73200
|
-
[maxlength]="maxLength()"
|
|
73201
|
-
[autoGrow]="true"
|
|
73202
|
-
[rows]="1"
|
|
73203
|
-
[value]="body()"
|
|
73204
|
-
[spellcheck]="false"
|
|
73205
|
-
autocapitalize="sentences"
|
|
73206
|
-
autocorrect="off"
|
|
73207
|
-
enterkeyhint="enter"
|
|
73208
|
-
(ionInput)="onInput($event)"
|
|
73209
|
-
(keydown)="onKeydown($event)"
|
|
73210
|
-
(paste)="onPaste($event)"
|
|
73211
|
-
></ion-textarea>
|
|
73212
|
-
|
|
73213
|
-
@if (canSend()) {
|
|
73214
|
-
<button class="send-btn" [attr.aria-label]="t('send')" (click)="onSend()">
|
|
73215
|
-
<ion-icon name="arrow-up-outline" aria-hidden="true" />
|
|
73216
|
-
</button>
|
|
73217
|
-
} @else if (showMic()) {
|
|
73218
|
-
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="startRecording()">
|
|
73219
|
-
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73220
|
-
</button>
|
|
73221
|
-
}
|
|
73222
|
-
</div>
|
|
73223
|
-
}
|
|
73058
|
+
} @else if (showMic()) {
|
|
73059
|
+
<button class="icon-btn mic" [attr.aria-label]="t('voice')" (click)="voice.emit()">
|
|
73060
|
+
<ion-icon name="mic-outline" aria-hidden="true" />
|
|
73061
|
+
</button>
|
|
73062
|
+
}
|
|
73063
|
+
</div>
|
|
73224
73064
|
</div>
|
|
73225
|
-
`, 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)}.
|
|
73065
|
+
`, styles: [":host{display:block}.composer{display:flex;flex-direction:column;gap:6px;padding:8px 10px calc(8px + env(safe-area-inset-bottom,0px));background:var(--ion-background-color, #fff);border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.reply-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;border-left:3px solid var(--ion-color-primary);border-radius:8px;background:var(--ion-color-step-100, rgba(127, 127, 127, .1))}.reply-content{display:flex;flex-direction:column;flex:1;min-width:0;font-size:.8125rem}.reply-name{font-weight:700;color:var(--ion-color-primary)}.reply-body{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ion-color-medium, #92949c)}.emoji-row{display:flex;gap:4px;flex-wrap:wrap}.emoji{border:none;background:var(--ion-color-step-100, rgba(127, 127, 127, .1));border-radius:10px;padding:4px 8px;font-size:1.25rem;cursor:pointer}.pill{display:flex;align-items:flex-end;gap:4px;background:var(--ion-color-light, #f4f5f8);border:1px solid var(--ion-border-color, rgba(0, 0, 0, .08));border-radius:24px;padding:2px 6px}.field{flex:1;--background: transparent;--padding-start: 6px;--padding-end: 6px;--padding-top: 8px;--padding-bottom: 8px;margin:0;max-height:120px;font-size:.9375rem}.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:transparent;color:var(--ion-color-medium, #92949c);cursor:pointer;font-size:1.375rem}.icon-btn:hover,.icon-btn.active,.icon-btn.mic:hover{color:var(--ion-color-primary)}.send-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;flex-shrink:0;border:none;border-radius:50%;background:var(--ion-color-primary);color:var(--ion-color-primary-contrast, #fff);cursor:pointer;font-size:1.25rem;transition:transform .1s ease}.send-btn:active{transform:scale(.92)}\n"] }]
|
|
73226
73066
|
}], ctorParameters: () => [] });
|
|
73227
73067
|
|
|
73228
73068
|
/**
|
|
@@ -73610,7 +73450,7 @@ class ChatWindowComponent {
|
|
|
73610
73450
|
<div class="closed-banner">{{ t('conversationClosed') }}</div>
|
|
73611
73451
|
}
|
|
73612
73452
|
</div>
|
|
73613
|
-
`, isInline: true, styles: [":host{display:block;height:100%}.chat{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}.messages{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}.state{margin:auto;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ion-color-medium, #92949c);font-size:.9375rem;text-align:center;padding:24px}.state.empty ion-icon{font-size:2.5rem;opacity:.5}.date-sep{position:sticky;top:4px;z-index:1;display:flex;justify-content:center;margin:8px 0}.date-sep span{font-size:.75rem;color:var(--ion-color-medium, #92949c);background:var(--ion-color-step-100, rgba(127, 127, 127, .14));padding:3px 12px;border-radius:999px;backdrop-filter:blur(4px)}.scroll-down{position:absolute;right:14px;bottom:84px;width:40px;height:40px;border-radius:50%;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium, #92949c);box-shadow:0 2px 10px #00000026;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1.25rem;z-index:3}.scroll-down .dot{position:absolute;top:0;right:0;width:12px;height:12px;border-radius:50%;background:var(--ion-color-primary);border:2px solid var(--ion-card-background, #fff)}.closed-banner{text-align:center;padding:14px;color:var(--ion-color-medium, #92949c);font-size:.875rem;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: MessageBubbleComponent, selector: "val-message-bubble", inputs: ["msg", "showAvatar", "showName", "tail", "locale"], outputs: ["action"] }, { kind: "component", type: ChatComposerComponent, selector: "val-chat-composer", inputs: ["placeholder", "disabled", "maxLength", "replyingTo", "showAttach", "showMic"], outputs: ["send", "typing", "attach", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
|
|
73453
|
+
`, isInline: true, styles: [":host{display:block;height:100%}.chat{position:relative;display:flex;flex-direction:column;height:100%;min-height:0}.messages{flex:1;min-height:0;overflow-y:auto;padding:12px;display:flex;flex-direction:column;gap:0}.state{margin:auto;display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--ion-color-medium, #92949c);font-size:.9375rem;text-align:center;padding:24px}.state.empty ion-icon{font-size:2.5rem;opacity:.5}.date-sep{position:sticky;top:4px;z-index:1;display:flex;justify-content:center;margin:8px 0}.date-sep span{font-size:.75rem;color:var(--ion-color-medium, #92949c);background:var(--ion-color-step-100, rgba(127, 127, 127, .14));padding:3px 12px;border-radius:999px;backdrop-filter:blur(4px)}.scroll-down{position:absolute;right:14px;bottom:84px;width:40px;height:40px;border-radius:50%;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color, #fff));color:var(--ion-color-medium, #92949c);box-shadow:0 2px 10px #00000026;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:1.25rem;z-index:3}.scroll-down .dot{position:absolute;top:0;right:0;width:12px;height:12px;border-radius:50%;background:var(--ion-color-primary);border:2px solid var(--ion-card-background, #fff)}.closed-banner{text-align:center;padding:14px;color:var(--ion-color-medium, #92949c);font-size:.875rem;border-top:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: MessageBubbleComponent, selector: "val-message-bubble", inputs: ["msg", "showAvatar", "showName", "tail", "locale"], outputs: ["action"] }, { kind: "component", type: ChatComposerComponent, selector: "val-chat-composer", inputs: ["placeholder", "disabled", "maxLength", "replyingTo", "showAttach", "showEmoji", "showMic"], outputs: ["send", "typing", "attach", "voice", "cancelReply"] }, { kind: "component", type: TypingIndicatorComponent, selector: "val-typing-indicator", inputs: ["typingUsers"] }] }); }
|
|
73614
73454
|
}
|
|
73615
73455
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChatWindowComponent, decorators: [{
|
|
73616
73456
|
type: Component,
|