valtech-components 4.0.228 → 4.0.230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm2022/lib/components/molecules/chat-composer/chat-composer.component.mjs +431 -0
- package/esm2022/lib/components/molecules/chat-input/chat-input.component.mjs +1 -9
- package/esm2022/lib/components/molecules/conversation-list-item/conversation-list-item.component.mjs +108 -0
- package/esm2022/lib/components/molecules/message-bubble/message-bubble.component.mjs +274 -191
- package/esm2022/lib/components/molecules/typing-indicator/typing-indicator.component.mjs +11 -12
- package/esm2022/lib/components/organisms/chat-window/chat-window.component.mjs +213 -203
- package/esm2022/lib/components/organisms/thread-panel/thread-panel.component.mjs +13 -11
- package/esm2022/lib/services/chat/conversation.service.mjs +37 -14
- package/esm2022/lib/services/chat/types.mjs +1 -1
- package/esm2022/lib/services/i18n/default-content.mjs +5 -1
- package/esm2022/lib/shared/utils/datetime.mjs +54 -1
- package/esm2022/lib/version.mjs +2 -2
- package/esm2022/public-api.mjs +4 -2
- package/fesm2022/valtech-components.mjs +1120 -429
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/molecules/chat-composer/chat-composer.component.d.ts +75 -0
- package/lib/components/molecules/conversation-list-item/conversation-list-item.component.d.ts +30 -0
- package/lib/components/molecules/message-bubble/message-bubble.component.d.ts +34 -32
- package/lib/components/molecules/typing-indicator/typing-indicator.component.d.ts +5 -4
- package/lib/components/organisms/chat-window/chat-window.component.d.ts +52 -56
- package/lib/components/organisms/thread-panel/thread-panel.component.d.ts +4 -1
- package/lib/services/chat/conversation.service.d.ts +12 -5
- package/lib/services/chat/types.d.ts +86 -52
- package/lib/shared/utils/datetime.d.ts +19 -0
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +3 -1
- package/src/lib/services/firebase/firebase-messaging-sw.js +70 -125
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
2
|
+
import { MessageReplyTo } from '../../../services/chat/types';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
type PendingKind = 'image' | 'audio' | 'file';
|
|
5
|
+
interface PendingAttachment {
|
|
6
|
+
id: number;
|
|
7
|
+
file: File;
|
|
8
|
+
url?: string;
|
|
9
|
+
kind: PendingKind;
|
|
10
|
+
}
|
|
11
|
+
/** Evento de envío: texto (caption) + adjuntos staged. files vacío => solo texto. */
|
|
12
|
+
export interface ChatComposerSendEvent {
|
|
13
|
+
text: string;
|
|
14
|
+
files: File[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* val-chat-composer — barra de redacción de chat moderna (signal inputs).
|
|
18
|
+
*
|
|
19
|
+
* Pill con: adjuntar (+), textarea auto-grow, y mic/enviar (mic cuando está vacío,
|
|
20
|
+
* enviar cuando hay texto o adjuntos). Los adjuntos (botón + / pegar imagen) se
|
|
21
|
+
* acumulan como PREVIEW (miniaturas) y se emiten por `(attach)` recién al ENVIAR
|
|
22
|
+
* — no se van solos al chat. Audio grabado se emite directo al parar.
|
|
23
|
+
* No incluye picker de emoji: el teclado del sistema ya lo trae.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ChatComposerComponent implements OnDestroy {
|
|
26
|
+
private i18n;
|
|
27
|
+
readonly placeholder: import("@angular/core").InputSignal<string>;
|
|
28
|
+
readonly disabled: import("@angular/core").InputSignal<boolean>;
|
|
29
|
+
readonly maxLength: import("@angular/core").InputSignal<number>;
|
|
30
|
+
readonly replyingTo: import("@angular/core").InputSignal<MessageReplyTo>;
|
|
31
|
+
readonly showAttach: import("@angular/core").InputSignal<boolean>;
|
|
32
|
+
readonly showMic: import("@angular/core").InputSignal<boolean>;
|
|
33
|
+
readonly send: import("@angular/core").OutputEmitterRef<ChatComposerSendEvent>;
|
|
34
|
+
readonly typing: import("@angular/core").OutputEmitterRef<void>;
|
|
35
|
+
readonly voice: import("@angular/core").OutputEmitterRef<void>;
|
|
36
|
+
readonly cancelReply: import("@angular/core").OutputEmitterRef<void>;
|
|
37
|
+
protected readonly body: import("@angular/core").WritableSignal<string>;
|
|
38
|
+
protected readonly pending: import("@angular/core").WritableSignal<PendingAttachment[]>;
|
|
39
|
+
private pendingId;
|
|
40
|
+
protected readonly recording: import("@angular/core").WritableSignal<boolean>;
|
|
41
|
+
private readonly recSeconds;
|
|
42
|
+
private recorder?;
|
|
43
|
+
private recStream?;
|
|
44
|
+
private recChunks;
|
|
45
|
+
private recTimer?;
|
|
46
|
+
protected readonly recTimeLabel: import("@angular/core").Signal<string>;
|
|
47
|
+
private lastTypingEmit;
|
|
48
|
+
constructor();
|
|
49
|
+
protected readonly canSend: import("@angular/core").Signal<boolean>;
|
|
50
|
+
protected readonly placeholderText: import("@angular/core").Signal<string>;
|
|
51
|
+
protected onInput(event: Event): void;
|
|
52
|
+
protected onKeydown(event: KeyboardEvent): void;
|
|
53
|
+
protected onSend(): void;
|
|
54
|
+
protected onFile(event: Event): void;
|
|
55
|
+
/** Pegar imagen desde el portapapeles (captura de pantalla, copiar imagen). */
|
|
56
|
+
protected onPaste(event: ClipboardEvent): void;
|
|
57
|
+
private addPending;
|
|
58
|
+
protected removePending(id: number): void;
|
|
59
|
+
/**
|
|
60
|
+
* Emite typing como máximo 1 vez cada 2s. performance.now es seguro en runtime.
|
|
61
|
+
*/
|
|
62
|
+
private emitTypingDebounced;
|
|
63
|
+
/** Selecciona un mimeType de audio soportado por el navegador. */
|
|
64
|
+
private pickAudioMime;
|
|
65
|
+
protected startRecording(): Promise<void>;
|
|
66
|
+
/** Para la grabación y la stagea como adjunto pendiente (con preview de audio). */
|
|
67
|
+
protected finishRecording(): void;
|
|
68
|
+
protected cancelRecording(): void;
|
|
69
|
+
private cleanupRecording;
|
|
70
|
+
ngOnDestroy(): void;
|
|
71
|
+
t(key: string): string;
|
|
72
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ChatComposerComponent, never>;
|
|
73
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ChatComposerComponent, "val-chat-composer", never, { "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "replyingTo": { "alias": "replyingTo"; "required": false; "isSignal": true; }; "showAttach": { "alias": "showAttach"; "required": false; "isSignal": true; }; "showMic": { "alias": "showMic"; "required": false; "isSignal": true; }; }, { "send": "send"; "typing": "typing"; "voice": "voice"; "cancelReply": "cancelReply"; }, never, never, true, never>;
|
|
74
|
+
}
|
|
75
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/**
|
|
3
|
+
* val-conversation-list-item — fila de lista de conversaciones estilo
|
|
4
|
+
* Telegram/WhatsApp: avatar (imagen o iniciales), nombre + hora relativa,
|
|
5
|
+
* preview del último mensaje + badge de no-leídos. Signal inputs, dark-safe.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* <val-conversation-list-item
|
|
9
|
+
* [name]="conv.name"
|
|
10
|
+
* [lastMessage]="conv.lastMsgPreview"
|
|
11
|
+
* [timestamp]="conv.lastMsgAt"
|
|
12
|
+
* [unreadCount]="conv.unread"
|
|
13
|
+
* (select)="open(conv.convId)" />
|
|
14
|
+
*/
|
|
15
|
+
export declare class ConversationListItemComponent {
|
|
16
|
+
private i18n;
|
|
17
|
+
readonly name: import("@angular/core").InputSignal<string>;
|
|
18
|
+
readonly avatarUrl: import("@angular/core").InputSignal<string>;
|
|
19
|
+
readonly lastMessage: import("@angular/core").InputSignal<string>;
|
|
20
|
+
readonly timestamp: import("@angular/core").InputSignal<string | Date>;
|
|
21
|
+
readonly unreadCount: import("@angular/core").InputSignal<number>;
|
|
22
|
+
readonly locale: import("@angular/core").InputSignal<string>;
|
|
23
|
+
readonly select: import("@angular/core").OutputEmitterRef<void>;
|
|
24
|
+
constructor();
|
|
25
|
+
protected readonly initials: import("@angular/core").Signal<string>;
|
|
26
|
+
protected readonly timeLabel: import("@angular/core").Signal<string>;
|
|
27
|
+
protected emptyText(): string;
|
|
28
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ConversationListItemComponent, never>;
|
|
29
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ConversationListItemComponent, "val-conversation-list-item", never, { "name": { "alias": "name"; "required": false; "isSignal": true; }; "avatarUrl": { "alias": "avatarUrl"; "required": false; "isSignal": true; }; "lastMessage": { "alias": "lastMessage"; "required": false; "isSignal": true; }; "timestamp": { "alias": "timestamp"; "required": false; "isSignal": true; }; "unreadCount": { "alias": "unreadCount"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; }, { "select": "select"; }, never, never, true, never>;
|
|
30
|
+
}
|
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { I18nService } from '../../../services/i18n';
|
|
3
|
-
import { MessageBubbleMetadata, MessageReactionClickEvent, MessageReplyClickEvent, MessageDeleteClickEvent, MessageEditClickEvent } from './types';
|
|
4
|
-
import { MessageReaction } from '../../../services/chat/types';
|
|
1
|
+
import { MessageBubbleMetadata } from './types';
|
|
5
2
|
import * as i0 from "@angular/core";
|
|
3
|
+
/** Acción disparada desde una burbuja. Un solo output tipado en vez de 4. */
|
|
4
|
+
export interface MessageBubbleAction {
|
|
5
|
+
type: 'reply' | 'edit' | 'delete' | 'react';
|
|
6
|
+
msgId: string;
|
|
7
|
+
/** Token del emoji, solo para type 'react'. */
|
|
8
|
+
token?: string;
|
|
9
|
+
}
|
|
6
10
|
/**
|
|
7
|
-
* val-message-bubble
|
|
11
|
+
* val-message-bubble — burbuja de mensaje presentacional (signal inputs).
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* Sin lógica de negocio: el contenedor (`val-chat-window`) decide agrupación y
|
|
14
|
+
* pasa flags (showAvatar/showName/tail). Las acciones (responder/editar/borrar/
|
|
15
|
+
* reaccionar) NO están siempre visibles: se revelan en hover (desktop) o al tocar
|
|
16
|
+
* la burbuja (móvil), y se emiten por un único `(action)` tipado.
|
|
12
17
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* [props]="msg"
|
|
16
|
-
* (reactionClick)="onReaction($event)"
|
|
17
|
-
* (replyClick)="onReply($event)"
|
|
18
|
-
* (deleteClick)="onDelete($event)"
|
|
19
|
-
* />
|
|
18
|
+
* Read receipts estilo WhatsApp: sending → reloj, sent → ✓, delivered/read → ✓✓
|
|
19
|
+
* (read en color), failed → alerta.
|
|
20
20
|
*/
|
|
21
|
-
export declare class MessageBubbleComponent
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
export declare class MessageBubbleComponent {
|
|
22
|
+
private i18n;
|
|
23
|
+
readonly msg: import("@angular/core").InputSignal<MessageBubbleMetadata>;
|
|
24
|
+
readonly showAvatar: import("@angular/core").InputSignal<boolean>;
|
|
25
|
+
readonly showName: import("@angular/core").InputSignal<boolean>;
|
|
26
|
+
readonly tail: import("@angular/core").InputSignal<boolean>;
|
|
27
|
+
readonly locale: import("@angular/core").InputSignal<string>;
|
|
28
|
+
readonly action: import("@angular/core").OutputEmitterRef<MessageBubbleAction>;
|
|
29
|
+
protected readonly actionsOpen: import("@angular/core").WritableSignal<boolean>;
|
|
28
30
|
constructor();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
protected readonly time: import("@angular/core").Signal<string>;
|
|
32
|
+
protected readonly initials: import("@angular/core").Signal<string>;
|
|
33
|
+
protected readonly imageAttachments: import("@angular/core").Signal<import("valtech-components").MessageAttachment[]>;
|
|
34
|
+
protected readonly audioAttachments: import("@angular/core").Signal<import("valtech-components").MessageAttachment[]>;
|
|
35
|
+
protected readonly fileAttachments: import("@angular/core").Signal<import("valtech-components").MessageAttachment[]>;
|
|
36
|
+
protected readonly statusIcon: import("@angular/core").Signal<"time-outline" | "alert-circle" | "checkmark-done" | "checkmark">;
|
|
37
|
+
protected toggleActions(): void;
|
|
38
|
+
protected emit(type: MessageBubbleAction['type'], token?: string): void;
|
|
39
|
+
t(key: string): string;
|
|
38
40
|
static ɵfac: i0.ɵɵFactoryDeclaration<MessageBubbleComponent, never>;
|
|
39
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<MessageBubbleComponent, "val-message-bubble", never, { "
|
|
41
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<MessageBubbleComponent, "val-message-bubble", never, { "msg": { "alias": "msg"; "required": true; "isSignal": true; }; "showAvatar": { "alias": "showAvatar"; "required": false; "isSignal": true; }; "showName": { "alias": "showName"; "required": false; "isSignal": true; }; "tail": { "alias": "tail"; "required": false; "isSignal": true; }; "locale": { "alias": "locale"; "required": false; "isSignal": true; }; }, { "action": "action"; }, never, never, true, never>;
|
|
40
42
|
}
|
|
@@ -3,17 +3,18 @@ import * as i0 from "@angular/core";
|
|
|
3
3
|
/**
|
|
4
4
|
* val-typing-indicator
|
|
5
5
|
*
|
|
6
|
-
* Muestra quienes estan escribiendo
|
|
7
|
-
*
|
|
6
|
+
* Muestra quienes estan escribiendo con animacion de tres puntos. Se oculta
|
|
7
|
+
* si `typingUsers` esta vacio. Usa signal input para que `typingText` (computed)
|
|
8
|
+
* recompute al cambiar la lista.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
11
|
* <val-typing-indicator [typingUsers]="['Maria', 'Carlos']" />
|
|
11
12
|
*/
|
|
12
13
|
export declare class TypingIndicatorComponent {
|
|
13
14
|
protected i18n: I18nService;
|
|
14
|
-
typingUsers: string[]
|
|
15
|
+
readonly typingUsers: import("@angular/core").InputSignal<string[]>;
|
|
15
16
|
constructor();
|
|
16
17
|
protected typingText: import("@angular/core").Signal<string>;
|
|
17
18
|
static ɵfac: i0.ɵɵFactoryDeclaration<TypingIndicatorComponent, never>;
|
|
18
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<TypingIndicatorComponent, "val-typing-indicator", never, { "typingUsers": { "alias": "typingUsers"; "required": false; }; }, {}, never, never, true, never>;
|
|
19
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<TypingIndicatorComponent, "val-typing-indicator", never, { "typingUsers": { "alias": "typingUsers"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
19
20
|
}
|
|
@@ -1,68 +1,64 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { MessageBubbleMetadata
|
|
1
|
+
import { MessageReplyTo } from '../../../services/chat/types';
|
|
2
|
+
import { MessageBubbleAction } from '../../molecules/message-bubble/message-bubble.component';
|
|
3
|
+
import { MessageBubbleMetadata } from '../../molecules/message-bubble/types';
|
|
4
|
+
import { ChatComposerSendEvent } from '../../molecules/chat-composer/chat-composer.component';
|
|
4
5
|
import * as i0 from "@angular/core";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
6
|
+
type ChatRow = {
|
|
7
|
+
kind: 'date';
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
} | {
|
|
11
|
+
kind: 'msg';
|
|
12
|
+
id: string;
|
|
13
|
+
msg: MessageBubbleMetadata;
|
|
14
|
+
showName: boolean;
|
|
15
|
+
showAvatar: boolean;
|
|
16
|
+
tail: boolean;
|
|
17
|
+
};
|
|
10
18
|
/**
|
|
11
|
-
* val-chat-window
|
|
19
|
+
* val-chat-window — ventana de chat completa (signal inputs).
|
|
12
20
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
21
|
+
* Agrupa mensajes consecutivos del mismo emisor y por día (separadores sticky),
|
|
22
|
+
* muestra avatar/nombre solo en los bordes del grupo, scroll-to-bottom inteligente
|
|
23
|
+
* (auto-scroll si estás abajo, pill "mensajes nuevos" si estás leyendo arriba),
|
|
24
|
+
* composer moderno con contexto de respuesta, y typing indicator.
|
|
17
25
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* [messages]="messages()"
|
|
22
|
-
* [currentUserId]="userId"
|
|
23
|
-
* [isOpen]="conv.status === 'open'"
|
|
24
|
-
* [typingUsers]="typingUsers()"
|
|
25
|
-
* (sendMessage)="onSend($event)"
|
|
26
|
-
* (reactionClick)="onReaction($event)"
|
|
27
|
-
* />
|
|
26
|
+
* API pública estable (consumida por thread-panel y showcase): inputs convId,
|
|
27
|
+
* messages, currentUserId, isOpen, typingUsers, isLoading; outputs sendMessage,
|
|
28
|
+
* loadMore, reactionClick, deleteMessage, replyTo (+ editMessage/attach/voice nuevos).
|
|
28
29
|
*/
|
|
29
|
-
export declare class ChatWindowComponent
|
|
30
|
-
|
|
31
|
-
convId: string
|
|
32
|
-
messages: MessageBubbleMetadata[]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
reactionClick: EventEmitter<{
|
|
30
|
+
export declare class ChatWindowComponent {
|
|
31
|
+
private i18n;
|
|
32
|
+
readonly convId: import("@angular/core").InputSignal<string>;
|
|
33
|
+
readonly messages: import("@angular/core").InputSignal<MessageBubbleMetadata[]>;
|
|
34
|
+
readonly currentUserId: import("@angular/core").InputSignal<string>;
|
|
35
|
+
readonly isOpen: import("@angular/core").InputSignal<boolean>;
|
|
36
|
+
readonly typingUsers: import("@angular/core").InputSignal<string[]>;
|
|
37
|
+
readonly isLoading: import("@angular/core").InputSignal<boolean>;
|
|
38
|
+
readonly sendMessage: import("@angular/core").OutputEmitterRef<ChatComposerSendEvent>;
|
|
39
|
+
readonly loadMore: import("@angular/core").OutputEmitterRef<void>;
|
|
40
|
+
readonly reactionClick: import("@angular/core").OutputEmitterRef<{
|
|
41
41
|
msgId: string;
|
|
42
42
|
token: string;
|
|
43
43
|
}>;
|
|
44
|
-
deleteMessage:
|
|
45
|
-
replyTo:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
|
|
44
|
+
readonly deleteMessage: import("@angular/core").OutputEmitterRef<string>;
|
|
45
|
+
readonly replyTo: import("@angular/core").OutputEmitterRef<string>;
|
|
46
|
+
readonly editMessage: import("@angular/core").OutputEmitterRef<string>;
|
|
47
|
+
readonly typing: import("@angular/core").OutputEmitterRef<void>;
|
|
48
|
+
readonly voice: import("@angular/core").OutputEmitterRef<void>;
|
|
49
|
+
private readonly msgsEl;
|
|
50
|
+
protected readonly replyingTo: import("@angular/core").WritableSignal<MessageReplyTo>;
|
|
51
|
+
protected readonly atBottom: import("@angular/core").WritableSignal<boolean>;
|
|
52
|
+
protected readonly hasNew: import("@angular/core").WritableSignal<boolean>;
|
|
53
|
+
protected readonly locale: import("@angular/core").Signal<"es-CL" | "en-US">;
|
|
51
54
|
constructor();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
protected closedText: import("@angular/core").Signal<string>;
|
|
59
|
-
protected loadingText: import("@angular/core").Signal<string>;
|
|
60
|
-
protected onReactionClick(event: MessageReactionClickEvent): void;
|
|
61
|
-
protected onEditClick(_msgId: string): void;
|
|
62
|
-
protected onTyping(): void;
|
|
63
|
-
private scrollToBottom;
|
|
64
|
-
private formatDateLabel;
|
|
55
|
+
protected readonly rows: import("@angular/core").Signal<ChatRow[]>;
|
|
56
|
+
protected onScroll(): void;
|
|
57
|
+
protected scrollToBottom(smooth: boolean): void;
|
|
58
|
+
protected onSend(event: ChatComposerSendEvent): void;
|
|
59
|
+
protected onAction(event: MessageBubbleAction): void;
|
|
60
|
+
t(key: string): string;
|
|
65
61
|
static ɵfac: i0.ɵɵFactoryDeclaration<ChatWindowComponent, never>;
|
|
66
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<ChatWindowComponent, "val-chat-window", never, { "convId": { "alias": "convId"; "required": false; }; "messages": { "alias": "messages"; "required": false;
|
|
62
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ChatWindowComponent, "val-chat-window", never, { "convId": { "alias": "convId"; "required": false; "isSignal": true; }; "messages": { "alias": "messages"; "required": false; "isSignal": true; }; "currentUserId": { "alias": "currentUserId"; "required": false; "isSignal": true; }; "isOpen": { "alias": "isOpen"; "required": false; "isSignal": true; }; "typingUsers": { "alias": "typingUsers"; "required": false; "isSignal": true; }; "isLoading": { "alias": "isLoading"; "required": false; "isSignal": true; }; }, { "sendMessage": "sendMessage"; "loadMore": "loadMore"; "reactionClick": "reactionClick"; "deleteMessage": "deleteMessage"; "replyTo": "replyTo"; "editMessage": "editMessage"; "typing": "typing"; "voice": "voice"; }, never, never, true, never>;
|
|
67
63
|
}
|
|
68
64
|
export {};
|
|
@@ -42,7 +42,10 @@ export declare class ThreadPanelComponent implements OnInit, OnDestroy {
|
|
|
42
42
|
protected isConvOpen: import("@angular/core").Signal<boolean>;
|
|
43
43
|
protected loadingText: import("@angular/core").Signal<string>;
|
|
44
44
|
protected bubbleMessages: import("@angular/core").Signal<MessageBubbleMetadata[]>;
|
|
45
|
-
protected onSend(
|
|
45
|
+
protected onSend(event: {
|
|
46
|
+
text: string;
|
|
47
|
+
files: File[];
|
|
48
|
+
}): void;
|
|
46
49
|
protected onReactionClick(event: {
|
|
47
50
|
msgId: string;
|
|
48
51
|
token: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Observable } from 'rxjs';
|
|
2
|
-
import { Conversation, ConversationType, CreateConversationRequest, ListConversationsResponse, Message, UpdateConversationRequest } from './types';
|
|
2
|
+
import { Conversation, ConversationType, CreateConversationRequest, ListConversationsResponse, Message, MessageAttachment, UpdateConversationRequest } from './types';
|
|
3
3
|
import * as i0 from "@angular/core";
|
|
4
4
|
/**
|
|
5
5
|
* ConversationService
|
|
@@ -33,12 +33,17 @@ export declare class ConversationService {
|
|
|
33
33
|
private get baseUrl();
|
|
34
34
|
/**
|
|
35
35
|
* Crea una nueva conversacion.
|
|
36
|
+
* El backend responde con un envelope { operationId, conversation } — lo
|
|
37
|
+
* desenvolvemos para devolver la Conversation directa.
|
|
36
38
|
*/
|
|
37
39
|
createConversation(req: CreateConversationRequest): Observable<Conversation>;
|
|
38
40
|
/**
|
|
39
|
-
* Obtiene o crea una conversacion asociada a una entidad (post, request, etc.).
|
|
41
|
+
* Obtiene o crea una conversacion asociada a una entidad (post, request, org, etc.).
|
|
40
42
|
* Usa el patron upsert del backend: si ya existe una conversacion para esa entidad,
|
|
41
43
|
* la devuelve; si no, la crea.
|
|
44
|
+
*
|
|
45
|
+
* El backend espera { entityRef: { type, id }, type } y responde con un envelope
|
|
46
|
+
* { operationId, conversation, created }.
|
|
42
47
|
*/
|
|
43
48
|
getOrCreateForEntity(entityType: string, entityId: string, type?: ConversationType): Observable<Conversation>;
|
|
44
49
|
/**
|
|
@@ -50,7 +55,7 @@ export declare class ConversationService {
|
|
|
50
55
|
*/
|
|
51
56
|
getConversation(convId: string): Observable<Conversation>;
|
|
52
57
|
/**
|
|
53
|
-
* Actualiza el
|
|
58
|
+
* Actualiza el nombre o settings de una conversacion.
|
|
54
59
|
*/
|
|
55
60
|
updateConversation(convId: string, req: UpdateConversationRequest): Observable<Conversation>;
|
|
56
61
|
/**
|
|
@@ -70,9 +75,11 @@ export declare class ConversationService {
|
|
|
70
75
|
*/
|
|
71
76
|
removeParticipant(convId: string, userId: string): Observable<void>;
|
|
72
77
|
/**
|
|
73
|
-
* Envia un mensaje a una conversacion.
|
|
78
|
+
* Envia un mensaje a una conversacion, opcionalmente con adjuntos.
|
|
79
|
+
* El backend espera `attachments: { url, type, name, size }[]` (key `type` = MIME),
|
|
80
|
+
* mientras que el front usa `mimeType` — se mapea aqui.
|
|
74
81
|
*/
|
|
75
|
-
sendMessage(convId: string, body: string): Observable<Message>;
|
|
82
|
+
sendMessage(convId: string, body: string, attachments?: MessageAttachment[]): Observable<Message>;
|
|
76
83
|
/**
|
|
77
84
|
* Elimina (marca como eliminado) un mensaje.
|
|
78
85
|
*/
|
|
@@ -7,50 +7,75 @@
|
|
|
7
7
|
* /apps/{appId}/orgs/{orgId}/conversations/{convId}/typing/{userId}
|
|
8
8
|
*/
|
|
9
9
|
import { FirestoreDocument } from '../firebase/types';
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
joinedAt: Date;
|
|
24
|
-
/** Ultimo mensaje leido por este participante. */
|
|
25
|
-
lastReadMsgId?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Tipo de conversacion. Debe coincidir con el backend (services/conversation):
|
|
12
|
+
* thread = comentarios en entidad · group = canal grupal · dm = 1:1 directo.
|
|
13
|
+
*/
|
|
14
|
+
export type ConversationType = 'thread' | 'group' | 'dm';
|
|
15
|
+
/** Visibilidad de una conversacion (quien puede unirse). */
|
|
16
|
+
export type ConvVisibility = 'org' | 'private' | 'public' | '';
|
|
17
|
+
/** Vinculo de la conversacion con una entidad del dominio. */
|
|
18
|
+
export interface EntityRef {
|
|
19
|
+
/** Tipo de entidad ('post' | 'request' | 'org' | 'custom', etc.). */
|
|
20
|
+
type: string;
|
|
21
|
+
/** ID de la entidad. */
|
|
22
|
+
id: string;
|
|
26
23
|
}
|
|
27
|
-
/**
|
|
28
|
-
export interface
|
|
24
|
+
/** Configuracion de la conversacion. */
|
|
25
|
+
export interface ConvSettings {
|
|
26
|
+
/** Si la conversacion esta abierta para enviar mensajes. */
|
|
27
|
+
isOpen: boolean;
|
|
28
|
+
/** Quien puede unirse. */
|
|
29
|
+
visibility: ConvVisibility;
|
|
30
|
+
/** Si admite participantes anonimos. */
|
|
31
|
+
allowAnonymous: boolean;
|
|
32
|
+
/** Si requiere membresia de la org. */
|
|
33
|
+
requireMembership: boolean;
|
|
34
|
+
/** Maximo de participantes (0 = sin limite). */
|
|
35
|
+
maxParticipants: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Documento de conversacion. Refleja el contrato del backend
|
|
39
|
+
* (services/conversation/types.go) — la fuente de verdad es DynamoDB y el doc se
|
|
40
|
+
* proyecta a Firestore para las reglas de membresia. Por eso los campos son
|
|
41
|
+
* `convId`/`name`/`participants: string[]` y NO el shape antiguo.
|
|
42
|
+
*/
|
|
43
|
+
export interface Conversation {
|
|
44
|
+
/** ID de la conversacion. */
|
|
45
|
+
convId: string;
|
|
29
46
|
/** Tipo de conversacion. */
|
|
30
47
|
type: ConversationType;
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
/** Fecha
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
|
|
48
|
+
/** ID de la app. */
|
|
49
|
+
appId: string;
|
|
50
|
+
/** ID de la org. */
|
|
51
|
+
orgId: string;
|
|
52
|
+
/** Nombre de la conversacion (para grupos). */
|
|
53
|
+
name?: string;
|
|
54
|
+
/** Entidad asociada (para tipo thread). */
|
|
55
|
+
entityRef?: EntityRef;
|
|
56
|
+
/** IDs de los participantes. */
|
|
57
|
+
participants: string[];
|
|
58
|
+
/** Configuracion. */
|
|
59
|
+
settings: ConvSettings;
|
|
60
|
+
/** ID del usuario que la creo. */
|
|
61
|
+
createdBy: string;
|
|
62
|
+
/** Fecha de creacion (ISO string). */
|
|
63
|
+
createdAt: string;
|
|
64
|
+
/** Fecha de ultima actualizacion (ISO string). */
|
|
65
|
+
updatedAt: string;
|
|
66
|
+
/** Fecha del ultimo mensaje (ISO string). */
|
|
67
|
+
lastMsgAt?: string;
|
|
68
|
+
/** Preview del ultimo mensaje. */
|
|
69
|
+
lastMsgPreview?: string;
|
|
49
70
|
}
|
|
50
71
|
/** Tipo de mensaje. */
|
|
51
72
|
export type MessageType = 'text' | 'image' | 'file' | 'system';
|
|
52
|
-
/**
|
|
53
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Estado de envio/entrega de un mensaje (para read receipts estilo WhatsApp):
|
|
75
|
+
* sending → reloj · sent → ✓ · delivered → ✓✓ gris · read → ✓✓ primary · failed → alerta.
|
|
76
|
+
* El backend hoy solo emite hasta `sent`; los demas estan soportados para el futuro.
|
|
77
|
+
*/
|
|
78
|
+
export type MessageStatus = 'sending' | 'sent' | 'delivered' | 'read' | 'failed';
|
|
54
79
|
/** Archivo adjunto en un mensaje. */
|
|
55
80
|
export interface MessageAttachment {
|
|
56
81
|
/** Nombre del archivo. */
|
|
@@ -118,28 +143,37 @@ export interface TypingDocument extends FirestoreDocument {
|
|
|
118
143
|
export interface CreateConversationRequest {
|
|
119
144
|
/** Tipo de conversacion. */
|
|
120
145
|
type: ConversationType;
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
|
|
146
|
+
/** Nombre (para grupos). */
|
|
147
|
+
name?: string;
|
|
148
|
+
/** Entidad asociada (para tipo thread). */
|
|
149
|
+
entityRef?: EntityRef;
|
|
150
|
+
/** IDs de los participantes iniciales (ademas del creador). */
|
|
151
|
+
participants?: string[];
|
|
152
|
+
/** Configuracion inicial. */
|
|
153
|
+
settings?: Partial<ConvSettings>;
|
|
129
154
|
}
|
|
130
155
|
/** Request para actualizar una conversacion. */
|
|
131
156
|
export interface UpdateConversationRequest {
|
|
132
|
-
/** Nuevo
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
|
|
157
|
+
/** Nuevo nombre (opcional). */
|
|
158
|
+
name?: string;
|
|
159
|
+
/** Nueva configuracion (opcional). */
|
|
160
|
+
settings?: Partial<ConvSettings>;
|
|
161
|
+
}
|
|
162
|
+
/** Response que envuelve una sola conversacion (create / get / update / get-or-create). */
|
|
163
|
+
export interface ConversationResponse {
|
|
164
|
+
/** ID de operacion para tracing. */
|
|
165
|
+
operationId: string;
|
|
166
|
+
/** La conversacion. */
|
|
167
|
+
conversation: Conversation;
|
|
168
|
+
/** Si fue creada en esta llamada (solo get-or-create). */
|
|
169
|
+
created?: boolean;
|
|
136
170
|
}
|
|
137
171
|
/** Response de listado de conversaciones. */
|
|
138
172
|
export interface ListConversationsResponse {
|
|
173
|
+
/** ID de operacion para tracing. */
|
|
174
|
+
operationId: string;
|
|
139
175
|
/** Lista de conversaciones. */
|
|
140
176
|
conversations: Conversation[];
|
|
141
177
|
/** Cursor para paginacion. */
|
|
142
178
|
nextCursor?: string;
|
|
143
|
-
/** Si hay mas paginas. */
|
|
144
|
-
hasMore: boolean;
|
|
145
179
|
}
|
|
@@ -19,3 +19,22 @@ export type TimeOfDayKey = 'goodMorning' | 'goodAfternoon' | 'goodEvening';
|
|
|
19
19
|
* const text = i18n.t(key); // 'Buenos días' | 'Buenas tardes' | 'Buenas noches'
|
|
20
20
|
*/
|
|
21
21
|
export declare function getTimeOfDayKey(date?: Date): TimeOfDayKey;
|
|
22
|
+
/** Hora corta local, ej. "14:05". */
|
|
23
|
+
export declare function formatClockTime(value: Date | string | undefined | null, locale?: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* Etiqueta corta para listas de conversaciones (estilo Telegram/WhatsApp):
|
|
26
|
+
* hoy → hora ("14:05"), ayer → label `yesterday`, esta semana → día de semana,
|
|
27
|
+
* más viejo → fecha corta. `labels.today/yesterday` se inyectan ya traducidos.
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatRelativeTime(value: Date | string | undefined | null, locale?: string, labels?: {
|
|
30
|
+
today?: string;
|
|
31
|
+
yesterday?: string;
|
|
32
|
+
}): string;
|
|
33
|
+
/**
|
|
34
|
+
* Etiqueta para separadores de fecha dentro de un chat: "Hoy" / "Ayer" /
|
|
35
|
+
* fecha larga ("12 de junio de 2026"). `labels.today/yesterday` ya traducidos.
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatDateSeparator(value: Date | string | undefined | null, locale?: string, labels?: {
|
|
38
|
+
today?: string;
|
|
39
|
+
yesterday?: string;
|
|
40
|
+
}): string;
|
package/lib/version.d.ts
CHANGED
package/package.json
CHANGED
package/public-api.d.ts
CHANGED
|
@@ -474,10 +474,12 @@ export { ConversationService } from './lib/services/chat/conversation.service';
|
|
|
474
474
|
export { provideValtechChat, VALTECH_CHAT_CONFIG } from './lib/services/chat/config';
|
|
475
475
|
export type { ValtechChatConfig } from './lib/services/chat/config';
|
|
476
476
|
export * from './lib/services/chat/types';
|
|
477
|
-
export { MessageBubbleComponent } from './lib/components/molecules/message-bubble/message-bubble.component';
|
|
477
|
+
export { MessageBubbleComponent, type MessageBubbleAction, } from './lib/components/molecules/message-bubble/message-bubble.component';
|
|
478
478
|
export * from './lib/components/molecules/message-bubble/types';
|
|
479
479
|
export { ChatInputComponent } from './lib/components/molecules/chat-input/chat-input.component';
|
|
480
480
|
export * from './lib/components/molecules/chat-input/types';
|
|
481
|
+
export { ChatComposerComponent, type ChatComposerSendEvent, } from './lib/components/molecules/chat-composer/chat-composer.component';
|
|
482
|
+
export { ConversationListItemComponent } from './lib/components/molecules/conversation-list-item/conversation-list-item.component';
|
|
481
483
|
export { TypingIndicatorComponent } from './lib/components/molecules/typing-indicator/typing-indicator.component';
|
|
482
484
|
export { ChatWindowComponent } from './lib/components/organisms/chat-window/chat-window.component';
|
|
483
485
|
export * from './lib/components/organisms/chat-window/types';
|