vue-wiguet-chatweb 0.1.15 → 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/components/Chat.vue.d.ts +2 -1
- package/dist/components/MessageList.vue.d.ts +3 -2
- package/dist/components/Widget.vue.d.ts +2 -1
- package/dist/store/index.d.ts +4 -0
- package/dist/style.css +1 -1
- package/dist/vue-wiguet-chatweb.js +2001 -1905
- package/dist/vue-wiguet-chatweb.umd.cjs +6 -6
- package/package.json +1 -1
- package/src/components/Chat.vue +155 -57
- package/src/components/MessageList.vue +92 -10
- package/src/components/Widget.vue +2 -1
package/package.json
CHANGED
package/src/components/Chat.vue
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
:canLoadMoreMessages="messagesData.canLoadMoreMessages"
|
16
16
|
@loadMore="getMessages"
|
17
17
|
@retry="retryMessage"
|
18
|
+
@on-qualifying="(args) => onQualifying(args)"
|
18
19
|
/>
|
19
20
|
</div>
|
20
21
|
|
@@ -28,7 +29,11 @@
|
|
28
29
|
required
|
29
30
|
ref="textAreaRef"
|
30
31
|
@input="() => autoAdjustHeight()"
|
31
|
-
@keydown.enter
|
32
|
+
@keydown.enter="
|
33
|
+
(event) => {
|
34
|
+
!isMovil && event.preventDefault();
|
35
|
+
}
|
36
|
+
"
|
32
37
|
@keyup.enter="saltoDeLineaOEnviar"
|
33
38
|
/>
|
34
39
|
|
@@ -43,7 +48,15 @@
|
|
43
48
|
</template>
|
44
49
|
|
45
50
|
<script setup lang="ts">
|
46
|
-
import {
|
51
|
+
import {
|
52
|
+
ref,
|
53
|
+
onMounted,
|
54
|
+
nextTick,
|
55
|
+
PropType,
|
56
|
+
watch,
|
57
|
+
onUnmounted,
|
58
|
+
computed,
|
59
|
+
} from "vue";
|
47
60
|
import { v4 as uuidv4 } from "uuid";
|
48
61
|
|
49
62
|
import {
|
@@ -58,6 +71,7 @@ import {
|
|
58
71
|
getMessagesApi,
|
59
72
|
sendMessageApi,
|
60
73
|
setVistoToTrueApi,
|
74
|
+
updateMessageApi,
|
61
75
|
} from "../store/index";
|
62
76
|
import { getInformationApi } from "../store";
|
63
77
|
import MessageList from "./MessageList.vue";
|
@@ -84,6 +98,7 @@ const emit = defineEmits([
|
|
84
98
|
"new-message",
|
85
99
|
"clear-new-messages",
|
86
100
|
"not-viewed-total",
|
101
|
+
"onQualifying",
|
87
102
|
]);
|
88
103
|
|
89
104
|
const props = defineProps({
|
@@ -109,8 +124,8 @@ const messageContainerRef = ref<HTMLElement | null>(null);
|
|
109
124
|
watch(
|
110
125
|
() => props.visible,
|
111
126
|
async (current) => {
|
112
|
-
if(!current) return
|
113
|
-
|
127
|
+
if (!current) return;
|
128
|
+
|
114
129
|
emit("clear-new-messages");
|
115
130
|
scrollToBottom();
|
116
131
|
|
@@ -122,18 +137,23 @@ watch(
|
|
122
137
|
|
123
138
|
const resp = await getInformationApi(props.tokenAuth);
|
124
139
|
|
125
|
-
if(resp) {
|
140
|
+
if (resp) {
|
126
141
|
appChatId.value = resp.appChat.id;
|
127
142
|
}
|
128
143
|
}
|
129
144
|
);
|
130
145
|
|
131
146
|
function saltoDeLineaOEnviar(event: KeyboardEvent) {
|
147
|
+
if(isMovil.value) {
|
148
|
+
autoAdjustHeight();
|
149
|
+
return;
|
150
|
+
}
|
151
|
+
|
132
152
|
if (event.key === "Enter" && event.shiftKey) {
|
133
|
-
const val = (event.target as any)?.value ||
|
153
|
+
const val = (event.target as any)?.value || "";
|
134
154
|
(event.target as any).value = val + "\n";
|
135
|
-
autoAdjustHeight()
|
136
|
-
return
|
155
|
+
autoAdjustHeight();
|
156
|
+
return;
|
137
157
|
}
|
138
158
|
|
139
159
|
submitMessage(event);
|
@@ -149,7 +169,7 @@ const submitMessage = async (event: Event) => {
|
|
149
169
|
});
|
150
170
|
return;
|
151
171
|
}
|
152
|
-
|
172
|
+
|
153
173
|
if (!message.value.trim()) {
|
154
174
|
emit("show-toast", {
|
155
175
|
severity: "warn",
|
@@ -176,11 +196,11 @@ const submitMessage = async (event: Event) => {
|
|
176
196
|
msPersonaId: props.user.msPersonaId,
|
177
197
|
},
|
178
198
|
};
|
179
|
-
|
199
|
+
|
180
200
|
const idx = messagesData.value.data.push(newMessage) - 1;
|
181
|
-
|
182
|
-
sendApi(message.value, appChatId.value).then((newMsg)=>{
|
183
|
-
if(!newMsg) {
|
201
|
+
|
202
|
+
sendApi(message.value, appChatId.value).then((newMsg) => {
|
203
|
+
if (!newMsg) {
|
184
204
|
messagesData.value.data[idx].error = {
|
185
205
|
error: true,
|
186
206
|
id: newMessage.id,
|
@@ -194,13 +214,13 @@ const submitMessage = async (event: Event) => {
|
|
194
214
|
});
|
195
215
|
} else {
|
196
216
|
messagesData.value.data[idx] = newMsg;
|
197
|
-
|
198
|
-
const message = {...newMsg, chatId:newMessage.chatId }
|
217
|
+
|
218
|
+
const message = { ...newMsg, chatId: newMessage.chatId };
|
199
219
|
socketService.value?.emit(
|
200
|
-
|
220
|
+
"sendMessage",
|
201
221
|
{ roomId: information?.value?.appChat.id, message },
|
202
222
|
(response: any) => {
|
203
|
-
console.log(
|
223
|
+
console.log("🚀 ~ socketService.value.emit ~ response:", response);
|
204
224
|
}
|
205
225
|
);
|
206
226
|
}
|
@@ -208,7 +228,7 @@ const submitMessage = async (event: Event) => {
|
|
208
228
|
|
209
229
|
message.value = "";
|
210
230
|
scrollToBottom();
|
211
|
-
textAreaRef.value && (textAreaRef.value.style.height = "20px")
|
231
|
+
textAreaRef.value && (textAreaRef.value.style.height = "20px");
|
212
232
|
};
|
213
233
|
|
214
234
|
const sendApi = async (message: string, appChatId: string) => {
|
@@ -221,37 +241,35 @@ const sendApi = async (message: string, appChatId: string) => {
|
|
221
241
|
};
|
222
242
|
|
223
243
|
const getMessages = async () => {
|
224
|
-
|
225
244
|
const lastMessagesId = messagesData.value.data[0]?.id;
|
226
245
|
const body: ListMessageBody = {
|
227
246
|
lastMessagesId,
|
228
247
|
appChatId: appChatId.value,
|
229
|
-
limit: 10
|
248
|
+
limit: 10,
|
230
249
|
};
|
231
250
|
|
232
251
|
isLoading.value = true;
|
233
252
|
const resp = await getMessagesApi({ body, token: props.tokenAuth });
|
234
253
|
isLoading.value = false;
|
235
|
-
|
254
|
+
|
236
255
|
messagesData.value.data.unshift(
|
237
256
|
...resp.data.sort((a, b) => -b.createdAt.localeCompare(a.createdAt))
|
238
257
|
);
|
239
|
-
|
258
|
+
|
240
259
|
messagesData.value.canLoadMoreMessages =
|
241
260
|
resp.pagination.total > resp.pagination.size;
|
242
|
-
|
261
|
+
|
243
262
|
if (lastMessagesId && messageContainerRef.value?.scrollHeight) {
|
244
263
|
mantainElementsOnViewport(messageContainerRef.value?.scrollHeight);
|
245
264
|
}
|
246
|
-
|
265
|
+
|
247
266
|
if (!lastMessagesId) scrollToBottom();
|
248
267
|
};
|
249
268
|
|
250
269
|
const retryMessage = async (message: Message) => {
|
251
270
|
emit("show-confirm", async () => {
|
252
|
-
|
253
271
|
if (!message.error?.id) return;
|
254
|
-
|
272
|
+
|
255
273
|
const msg = await sendApi(message.message, appChatId.value);
|
256
274
|
|
257
275
|
if (!msg) {
|
@@ -267,13 +285,13 @@ const retryMessage = async (message: Message) => {
|
|
267
285
|
"id",
|
268
286
|
message.error.id
|
269
287
|
);
|
270
|
-
|
288
|
+
|
271
289
|
messagesData.value.data[idx] = { ...msg, error: undefined };
|
272
290
|
|
273
|
-
socketService.value?.emit(
|
274
|
-
|
275
|
-
|
276
|
-
);
|
291
|
+
socketService.value?.emit("sendMessage", {
|
292
|
+
roomId: information?.value?.appChat.id,
|
293
|
+
message,
|
294
|
+
});
|
277
295
|
}
|
278
296
|
|
279
297
|
scrollToBottom();
|
@@ -305,23 +323,28 @@ const fontSpace = 14;
|
|
305
323
|
function autoAdjustHeight() {
|
306
324
|
if (!textAreaRef.value) return;
|
307
325
|
|
308
|
-
textAreaRef.value.style.height =
|
326
|
+
textAreaRef.value.style.height =
|
327
|
+
textAreaRef.value.scrollHeight - fontSpace + "px";
|
328
|
+
|
329
|
+
if (textAreaRef.value.scrollHeight === textAreaRef.value.clientHeight)
|
330
|
+
return;
|
309
331
|
|
310
|
-
|
311
|
-
|
312
|
-
textAreaRef.value.style.height = textAreaRef.value.scrollHeight + "px"
|
332
|
+
textAreaRef.value.style.height = textAreaRef.value.scrollHeight + "px";
|
313
333
|
}
|
314
334
|
|
315
335
|
const socketService = ref<Socket>();
|
316
336
|
const information = ref<ChatInformation>();
|
317
337
|
|
318
|
-
function connectMsWebSocket
|
319
|
-
|
338
|
+
function connectMsWebSocket(
|
339
|
+
userChat: ChatInformation,
|
340
|
+
app: APP_TYPE = APP_TYPE.WEBCHAT
|
341
|
+
) {
|
342
|
+
if (!userChat) throw new Error("user chat is required");
|
320
343
|
|
321
344
|
socketService.value = io(window.VITE_SOCKET_URI, {
|
322
345
|
query: {
|
323
346
|
// TODO: confirmar si se quita o no
|
324
|
-
usuarioId: `${userChat?.chat?.persona?.funcionarioId}`,
|
347
|
+
usuarioId: `${userChat?.chat?.persona?.funcionarioId}`,
|
325
348
|
aplicacion: app,
|
326
349
|
},
|
327
350
|
extraHeaders: { Authorization: props.tokenAuth },
|
@@ -329,24 +352,24 @@ function connectMsWebSocket (userChat: ChatInformation ,app: APP_TYPE = APP_TYPE
|
|
329
352
|
|
330
353
|
socketService.value.removeAllListeners();
|
331
354
|
|
332
|
-
socketService.value.on(
|
333
|
-
console.log(
|
355
|
+
socketService.value.on("connect", () => {
|
356
|
+
console.log("Conectado al servidor de sockets");
|
334
357
|
|
335
|
-
socketService.value?.emit(
|
336
|
-
|
337
|
-
`${userChat?.appChat?.id}`
|
338
|
-
);
|
339
|
-
})
|
358
|
+
socketService.value?.emit("joinRoom", `${userChat?.appChat?.id}`);
|
359
|
+
});
|
340
360
|
|
341
|
-
socketService.value.on(
|
342
|
-
console.log(
|
361
|
+
socketService.value.on("disconnect", () => {
|
362
|
+
console.log("Desconectado del servidor de sockets");
|
343
363
|
});
|
344
364
|
|
345
|
-
socketService.value.on(
|
346
|
-
console.log(
|
347
|
-
|
348
|
-
if (
|
349
|
-
|
365
|
+
socketService.value.on("receiveMessage", (data: any) => {
|
366
|
+
console.log("Mensaje recibido:", data.message, userChat);
|
367
|
+
|
368
|
+
if (
|
369
|
+
data.message.sender.msPersonaId === userChat?.chat?.persona?.msPersonaId
|
370
|
+
)
|
371
|
+
return;
|
372
|
+
|
350
373
|
messagesData.value.data.push(data.message);
|
351
374
|
setVistoToTrueApi(data.message.appChatId, props.tokenAuth);
|
352
375
|
scrollToBottom();
|
@@ -354,17 +377,92 @@ function connectMsWebSocket (userChat: ChatInformation ,app: APP_TYPE = APP_TYPE
|
|
354
377
|
});
|
355
378
|
}
|
356
379
|
|
380
|
+
const isMovil = computed(() => {
|
381
|
+
return [OS.ANDROID, OS.IOS].includes(getOS() as OS);
|
382
|
+
});
|
383
|
+
|
384
|
+
const enum OS {
|
385
|
+
ANDROID = "Android",
|
386
|
+
IOS = "iPhone",
|
387
|
+
}
|
388
|
+
|
389
|
+
function getOS() {
|
390
|
+
const userAgent = window.navigator.userAgent;
|
391
|
+
const platform = (window.navigator as any)?.userAgentData?.platform || window.navigator.platform;
|
392
|
+
const macosPlatforms = [
|
393
|
+
"macOS",
|
394
|
+
"Macintosh",
|
395
|
+
"MacIntel",
|
396
|
+
"MacPPC",
|
397
|
+
"Mac68K",
|
398
|
+
];
|
399
|
+
const windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"];
|
400
|
+
const iosPlatforms = ["iPhone", "iPad", "iPod"];
|
401
|
+
|
402
|
+
let os = null;
|
403
|
+
|
404
|
+
if (macosPlatforms.indexOf(platform) !== -1) {
|
405
|
+
os = "Mac OS";
|
406
|
+
} else if (iosPlatforms.indexOf(platform) !== -1) {
|
407
|
+
os = OS.IOS;
|
408
|
+
} else if (windowsPlatforms.indexOf(platform) !== -1) {
|
409
|
+
os = "Windows";
|
410
|
+
} else if (/Android/.test(userAgent)) {
|
411
|
+
os = OS.ANDROID;
|
412
|
+
} else if (/Linux/.test(platform)) {
|
413
|
+
os = "Linux";
|
414
|
+
}
|
415
|
+
|
416
|
+
return os;
|
417
|
+
}
|
418
|
+
|
419
|
+
function onQualifying({ message, emoji }: { message: Message, emoji: {icon: string, value: number } }) {
|
420
|
+
const callback = async () => {
|
421
|
+
if (!message.id || !emoji) return;
|
422
|
+
|
423
|
+
const idx = searchFromLast<Message>(
|
424
|
+
messagesData.value.data,
|
425
|
+
"id",
|
426
|
+
message.id
|
427
|
+
);
|
428
|
+
|
429
|
+
let prevMessage = messagesData.value?.data?.[idx].message
|
430
|
+
messagesData.value?.data?.[idx] && (messagesData.value.data[idx].message = emoji.icon);
|
431
|
+
|
432
|
+
const msg = await updateMessageApi(message.id, { message: emoji.icon, tipoCalificacionId: emoji.value }, props.tokenAuth);
|
433
|
+
|
434
|
+
if (!msg) {
|
435
|
+
messagesData.value?.data?.[idx] && (messagesData.value.data[idx].message = prevMessage);
|
436
|
+
emit("show-toast", {
|
437
|
+
severity: "error",
|
438
|
+
summary: "Error",
|
439
|
+
detail: "Ocurrio un error al enviar el mensaje, intente nuevamente",
|
440
|
+
life: 5000,
|
441
|
+
});
|
442
|
+
return
|
443
|
+
}
|
444
|
+
|
445
|
+
socketService.value?.emit("sendMessage", {
|
446
|
+
roomId: information?.value?.appChat.id,
|
447
|
+
message,
|
448
|
+
});
|
449
|
+
}
|
450
|
+
|
451
|
+
emit('onQualifying', callback)
|
452
|
+
};
|
453
|
+
|
454
|
+
|
357
455
|
onMounted(async () => {
|
358
456
|
if (appChatId.value) return;
|
359
|
-
|
457
|
+
|
360
458
|
const resp = await getInformationApi(props.tokenAuth);
|
361
|
-
|
362
|
-
if(!resp) return;
|
363
|
-
|
459
|
+
|
460
|
+
if (!resp) return;
|
461
|
+
|
364
462
|
information.value = resp;
|
365
463
|
appChatId.value = resp.appChat.id;
|
366
464
|
notViewed.value = resp.appChat.totalNoVistosCliente;
|
367
|
-
connectMsWebSocket(resp)
|
465
|
+
connectMsWebSocket(resp);
|
368
466
|
getMessages();
|
369
467
|
emit("not-viewed-total", resp.appChat.totalNoVistosCliente);
|
370
468
|
});
|
@@ -26,7 +26,25 @@
|
|
26
26
|
<div class="chat-message">
|
27
27
|
<div class="bubble" :class="message.esCliente ? 'right' : 'left'">
|
28
28
|
<div :class="message.esCliente ? 'content-right' : 'content-left'">
|
29
|
-
<div
|
29
|
+
<div v-if="message.message === '😊😄🙂😐🙁'" class="flex gap-2" >
|
30
|
+
<div>
|
31
|
+
<strong style="display: block; margin-bottom: 0.5rem;">Ayúdanos a mejorar nuestro servicio.</strong>
|
32
|
+
<span>Que le pareció la atención:</span>
|
33
|
+
</div>
|
34
|
+
<a
|
35
|
+
v-for="emoji in emojis"
|
36
|
+
href="javascript:"
|
37
|
+
class="btn-icon"
|
38
|
+
:key="emoji.value"
|
39
|
+
@click="emit('onQualifying', { message, emoji })"
|
40
|
+
>
|
41
|
+
<div class="flex flex-col items-center">
|
42
|
+
<div class="icon">{{ emoji.icon }}</div>
|
43
|
+
<span>{{ emoji.label }}</span>
|
44
|
+
</div>
|
45
|
+
</a>
|
46
|
+
</div>
|
47
|
+
<div v-else class="message-text" style="white-space: pre-line" v-html="textToRichFormat(message.message)"></div>
|
30
48
|
<div class="detail-message flex justify-content-between">
|
31
49
|
<span class="mr-5" v-if="message.sender?.nombreCompleto">
|
32
50
|
{{
|
@@ -53,13 +71,13 @@
|
|
53
71
|
</template>
|
54
72
|
|
55
73
|
<script setup lang="ts">
|
56
|
-
import { PropType, onBeforeMount, ref, watch } from
|
74
|
+
import { PropType, onBeforeMount, ref, watch, h } from 'vue';
|
57
75
|
import { useIntersectionObserver } from "@vueuse/core";
|
58
76
|
import { type Message } from "../dto/app.dto";
|
59
77
|
import { DateTime } from "luxon";
|
60
78
|
import DangerIcon from "./DangerIcon.vue";
|
61
79
|
|
62
|
-
const emit = defineEmits(["loadMore", "retry"]);
|
80
|
+
const emit = defineEmits(["loadMore", "retry", "onQualifying"]);
|
63
81
|
const props = defineProps({
|
64
82
|
messages: {
|
65
83
|
type: Array as PropType<Message[]>,
|
@@ -98,15 +116,51 @@ watch(
|
|
98
116
|
}
|
99
117
|
);
|
100
118
|
|
101
|
-
|
102
|
-
|
103
|
-
|
119
|
+
type RichTextType = {
|
120
|
+
regex: RegExp;
|
121
|
+
tag: string;
|
122
|
+
};
|
123
|
+
|
124
|
+
const richText: { [key in string]: RichTextType } = {
|
125
|
+
BOLD: { regex: /\*(.+?)\*(?!\*)/g, tag: '<strong--custom-tag>{val}</strong--custom-tag>' },
|
126
|
+
ITALIC: { regex: /_(.+?)_/, tag: '<i--custom-tag>{val}</i--custom-tag>' },
|
127
|
+
CROSSED_OUT: { regex: /~(.+?)~(?!~)/g, tag: '<del--custom-tag>{val}</del--custom-tag>' },
|
128
|
+
URL: {
|
129
|
+
regex: /(http?s?:\/\/[^\s]+)/g,
|
130
|
+
tag: '<a--custom-tag href="{val}" target="_blank">{val}</a--custom-tag>',
|
131
|
+
},
|
132
|
+
};
|
133
|
+
|
134
|
+
function textToRichFormat(text: string) {
|
135
|
+
const richTextValues = Object.values(richText);
|
136
|
+
|
137
|
+
if (!richTextValues || (Array.isArray(richTextValues) && richTextValues.length === 0))
|
138
|
+
return text;
|
139
|
+
|
140
|
+
const newMessage = () => {
|
141
|
+
|
142
|
+
return richTextValues.reduce((textPrev, rtv) => {
|
143
|
+
return textPrev.replace(rtv.regex, (middleValueAssert, middleValue) => {
|
144
|
+
const regexVerifyExistTag = /--custom-tag/;
|
104
145
|
|
105
|
-
|
106
|
-
|
107
|
-
return
|
108
|
-
|
146
|
+
if (regexVerifyExistTag.test(middleValueAssert)) return middleValueAssert;
|
147
|
+
|
148
|
+
return rtv.tag.replace(/{val}/g, middleValue);
|
149
|
+
});
|
150
|
+
}, text);
|
151
|
+
};
|
152
|
+
|
153
|
+
return newMessage().replace(/--custom-tag/g, '');
|
109
154
|
}
|
155
|
+
|
156
|
+
const emojis = [
|
157
|
+
{ label: 'Excelente', value: 1, icon: '😊' },
|
158
|
+
{ label: 'Buena', value: 2, icon: '😄' },
|
159
|
+
{ label: 'Aceptable', value: 3, icon: '🙂' },
|
160
|
+
{ label: 'Mala', value: 4, icon: '😐' },
|
161
|
+
{ label: 'Muy Mala', value: 5, icon: '🙁' },
|
162
|
+
]
|
163
|
+
|
110
164
|
</script>
|
111
165
|
|
112
166
|
<style scoped>
|
@@ -232,4 +286,32 @@ function convertUrlsToLinks(content:string) {
|
|
232
286
|
.messages-container-list {
|
233
287
|
width: 100%;
|
234
288
|
}
|
289
|
+
|
290
|
+
.btn-icon {
|
291
|
+
text-decoration: none;
|
292
|
+
border: none;
|
293
|
+
}
|
294
|
+
.btn-icon span {
|
295
|
+
color: currentColor;
|
296
|
+
text-wrap: nowrap;
|
297
|
+
}
|
298
|
+
|
299
|
+
.btn-icon .icon {
|
300
|
+
font-size: 2rem;
|
301
|
+
}
|
302
|
+
|
303
|
+
.flex {
|
304
|
+
display: flex;
|
305
|
+
flex-wrap: wrap;
|
306
|
+
}
|
307
|
+
.flex-col {
|
308
|
+
flex-direction: column;
|
309
|
+
}
|
310
|
+
.items-center {
|
311
|
+
align-items: center;
|
312
|
+
}
|
313
|
+
.gap-2 {
|
314
|
+
gap: 0.5rem;
|
315
|
+
}
|
316
|
+
|
235
317
|
</style>
|
@@ -50,6 +50,7 @@
|
|
50
50
|
@clear-new-messages="newMessages = 0"
|
51
51
|
@new-message="() => newMessages++"
|
52
52
|
@not-viewed-total="(val) => (newMessages = val)"
|
53
|
+
@on-qualifying="(args)=> emit('onQualifying', args)"
|
53
54
|
/>
|
54
55
|
</div>
|
55
56
|
</div>
|
@@ -63,7 +64,7 @@ import IconTelegram from "./IconTelegram.vue";
|
|
63
64
|
import IconWhatsApp from "./IconWhatsApp.vue";
|
64
65
|
import IconChat from "./IconChat.vue";
|
65
66
|
|
66
|
-
const emit = defineEmits(["show-toast", "show-confirm"]);
|
67
|
+
const emit = defineEmits(["show-toast", "show-confirm", "onQualifying"]);
|
67
68
|
|
68
69
|
const enum MeansCommunication{
|
69
70
|
WHATSAPP,
|