mirror of
https://github.com/abhinavxd/libredesk.git
synced 2025-11-17 04:11:52 +00:00
feat(conversations): add trigram index for searching ref numbers
feat(messages): add trigram index for text content search - feat: UI animations for conversation and messages list. - Simplify websocket updates.
This commit is contained in:
@@ -1,44 +1,74 @@
|
||||
<template>
|
||||
<div ref="threadEl" class="overflow-y-scroll relative h-full" @scroll="handleScroll">
|
||||
<div class="min-h-full relative pb-20">
|
||||
<div class="text-center mt-3" v-if="conversationStore.messages.hasMore && !conversationStore.messages.loading">
|
||||
<Button variant="ghost" @click="conversationStore.fetchNextMessages">
|
||||
<RefreshCw size="17" class="mr-2" />
|
||||
Load more
|
||||
</Button>
|
||||
</div>
|
||||
<div v-for="message in conversationStore.conversationMessages" :key="message.uuid"
|
||||
:class="message.type === 'activity' ? 'm-4' : 'm-6'">
|
||||
<div v-if="conversationStore.messages.loading">
|
||||
<MessagesSkeleton></MessagesSkeleton>
|
||||
<div class="flex flex-col relative h-full">
|
||||
<div ref="threadEl" class="flex-1 overflow-y-auto" @scroll="handleScroll">
|
||||
<div class="min-h-full pb-20 px-4">
|
||||
<DotLoader v-if="conversationStore.messages.loading" />
|
||||
|
||||
<div
|
||||
class="text-center mt-3"
|
||||
v-if="conversationStore.messages.hasMore && !conversationStore.messages.loading"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@click="conversationStore.fetchNextMessages"
|
||||
class="transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-700 hover:scale-105 active:scale-95"
|
||||
>
|
||||
<RefreshCw size="17" class="mr-2" />
|
||||
Load more
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="!message.private">
|
||||
<ContactMessageBubble :message="message" v-if="message.type === 'incoming'" />
|
||||
<AgentMessageBubble :message="message" v-if="message.type === 'outgoing'" />
|
||||
|
||||
<TransitionGroup
|
||||
enter-active-class="animate-slide-in"
|
||||
leave-active-class="animate-slide-out"
|
||||
tag="div"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div
|
||||
v-for="message in conversationStore.conversationMessages"
|
||||
:key="message.uuid"
|
||||
:class="message.type === 'activity' ? 'my-2' : 'my-4'"
|
||||
>
|
||||
<div v-if="!message.private">
|
||||
<ContactMessageBubble :message="message" v-if="message.type === 'incoming'" />
|
||||
<AgentMessageBubble :message="message" v-if="message.type === 'outgoing'" />
|
||||
</div>
|
||||
<div v-else-if="isPrivateNote(message)">
|
||||
<AgentMessageBubble :message="message" v-if="message.type === 'outgoing'" />
|
||||
</div>
|
||||
<div v-else-if="message.type === 'activity'">
|
||||
<ActivityMessageBubble :message="message" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="isPrivateNote(message)">
|
||||
<AgentMessageBubble :message="message" v-if="message.type === 'outgoing'" />
|
||||
</div>
|
||||
<div v-else-if="message.type === 'activity'">
|
||||
<ActivityMessageBubble :message="message" />
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sticky container for the scroll arrow -->
|
||||
<div v-show="!isAtBottom" class="sticky bottom-6 flex justify-end px-6">
|
||||
<div class="relative">
|
||||
<button @click="handleScrollToBottom" class="w-8 h-8 rounded-full flex items-center justify-center shadow">
|
||||
<ArrowDown size="20" />
|
||||
<Transition
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
enter-from-class="opacity-0 translate-y-1"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition ease-in duration-150"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 translate-y-1"
|
||||
>
|
||||
<div v-show="!isAtBottom" class="absolute bottom-6 right-6 z-10">
|
||||
<button
|
||||
@click="handleScrollToBottom"
|
||||
class="w-10 h-10 rounded-full flex items-center justify-center shadow-lg border bg-white text-primary transition-colors duration-200 hover:bg-gray-100"
|
||||
>
|
||||
<ChevronDown size="18" />
|
||||
</button>
|
||||
<span v-if="unReadMessages > 0"
|
||||
class="absolute -top-1 -right-1 min-w-[20px] h-5 px-1.5 rounded-full bg-primary text-white text-xs font-medium flex items-center justify-center">
|
||||
<span
|
||||
v-if="unReadMessages > 0"
|
||||
class="absolute -top-1 -right-1 min-w-[20px] h-5 px-1.5 rounded-full bg-green-500 text-secondary text-xs font-medium flex items-center justify-center"
|
||||
>
|
||||
{{ unReadMessages }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -47,11 +77,11 @@ import { ref, onMounted, watch } from 'vue'
|
||||
import ContactMessageBubble from './ContactMessageBubble.vue'
|
||||
import ActivityMessageBubble from './ActivityMessageBubble.vue'
|
||||
import AgentMessageBubble from './AgentMessageBubble.vue'
|
||||
import MessagesSkeleton from './MessagesSkeleton.vue'
|
||||
import { DotLoader } from '@/components/ui/loader'
|
||||
import { useConversationStore } from '@/stores/conversation'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { RefreshCw, ArrowDown } from 'lucide-vue-next'
|
||||
import { RefreshCw, ChevronDown } from 'lucide-vue-next'
|
||||
import { useEmitter } from '@/composables/useEmitter'
|
||||
import { EMITTER_EVENTS } from '@/constants/emitterEvents'
|
||||
|
||||
@@ -61,6 +91,7 @@ const threadEl = ref(null)
|
||||
const emitter = useEmitter()
|
||||
const isAtBottom = ref(true)
|
||||
const unReadMessages = ref(0)
|
||||
const currentConversationUUID = ref('')
|
||||
|
||||
const checkIfAtBottom = () => {
|
||||
const thread = threadEl.value
|
||||
@@ -82,7 +113,6 @@ const handleScrollToBottom = () => {
|
||||
|
||||
const scrollToBottom = () => {
|
||||
setTimeout(() => {
|
||||
console.log('scrolling..')
|
||||
const thread = threadEl.value
|
||||
if (thread) {
|
||||
thread.scrollTop = thread.scrollHeight
|
||||
@@ -92,8 +122,11 @@ const scrollToBottom = () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scrollToBottom()
|
||||
checkIfAtBottom()
|
||||
handleNewMessage()
|
||||
})
|
||||
|
||||
const handleNewMessage = () => {
|
||||
emitter.on(EMITTER_EVENTS.NEW_MESSAGE, (data) => {
|
||||
if (data.conversation_uuid === conversationStore.current.uuid) {
|
||||
if (data.message.sender_id === userStore.userID) {
|
||||
@@ -103,18 +136,22 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// On conversation change scroll to the bottom
|
||||
watch(
|
||||
() => conversationStore.current.uuid,
|
||||
() => {
|
||||
unReadMessages.value = 0
|
||||
scrollToBottom()
|
||||
() => conversationStore.conversationMessages,
|
||||
(messages) => {
|
||||
// Scroll to bottom when conversation changes and there are new messages.
|
||||
// New messages on next db page should not scroll to bottom.
|
||||
if (messages.length > 0 && currentConversationUUID.value !== conversationStore.current?.uuid) {
|
||||
currentConversationUUID.value = conversationStore.current.uuid
|
||||
unReadMessages.value = 0
|
||||
scrollToBottom()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const isPrivateNote = (message) => {
|
||||
return message.type === 'outgoing' && message.private
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user