mirror of
https://github.com/chatwoot/chatwoot.git
synced 2026-06-13 21:01:16 +08:00
144 lines
4.0 KiB
Vue
144 lines
4.0 KiB
Vue
<script setup>
|
|
import { ref, onMounted, computed, watch, useTemplateRef } from 'vue';
|
|
import Message from 'next/message/Message.vue';
|
|
import TypingIndicator from 'next/message/TypingIndicator.vue';
|
|
import shouldGroupWithNext from 'next/message/groupWithNext.js';
|
|
import Snipper from 'next/spinner/Spinner.vue';
|
|
import LiteReplyBox from './LiteReplyBox.vue';
|
|
import {
|
|
useStore,
|
|
useMapGetter,
|
|
useFunctionGetter,
|
|
} from '../dashboard/composables/store';
|
|
import { getTypingUsersText } from '../dashboard/helper/commons';
|
|
import { useCamelCase } from '../dashboard/composables/useTransformKeys';
|
|
import { useInfiniteScroll, useThrottleFn } from '@vueuse/core';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useInbox } from '../dashboard/composables/useInbox';
|
|
|
|
const { isAnEmailChannel } = useInbox();
|
|
|
|
const conversationId = computed(() => {
|
|
// eslint-disable-next-line no-underscore-dangle
|
|
return window.__WOOT_CONVERSATION_ID__;
|
|
});
|
|
|
|
const { t } = useI18n();
|
|
|
|
const messageListRef = useTemplateRef('messageListRef');
|
|
const store = useStore();
|
|
const isAllLoaded = useMapGetter('getAllMessagesLoaded');
|
|
const isFetching = ref(false);
|
|
|
|
const typingUserList = useFunctionGetter(
|
|
'conversationTypingStatus/getUserList',
|
|
conversationId.value
|
|
);
|
|
|
|
const isAnyoneTyping = computed(() => {
|
|
return typingUserList.value.length > 0;
|
|
});
|
|
|
|
const typingUserNames = computed(() => {
|
|
if (!isAnyoneTyping.value) return '';
|
|
const [i18nKey, params] = getTypingUsersText(typingUserList.value);
|
|
// eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys
|
|
return t(i18nKey, params);
|
|
});
|
|
|
|
const conversation = computed(() => {
|
|
return store.getters.getConversationById(conversationId.value);
|
|
});
|
|
|
|
const allMessages = computed(() => {
|
|
if (!conversation.value) return [];
|
|
|
|
return useCamelCase(conversation.value.messages, { deep: true }).reverse();
|
|
});
|
|
|
|
const fetchMore = async () => {
|
|
if (isFetching.value) return;
|
|
if (!conversation?.value?.id) return;
|
|
if (!allMessages.value?.length) return;
|
|
|
|
try {
|
|
isFetching.value = true;
|
|
|
|
await store.dispatch('fetchPreviousMessages', {
|
|
conversationId: conversation.value.id,
|
|
before: allMessages.value[allMessages.value.length - 1].id,
|
|
});
|
|
} finally {
|
|
isFetching.value = false;
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await store.dispatch('inboxes/get');
|
|
await Promise.all([
|
|
store.dispatch('getConversation', conversationId.value),
|
|
store.dispatch('fetchAllAttachments', conversationId.value),
|
|
]);
|
|
});
|
|
|
|
watch(conversation, () => {
|
|
store.dispatch('setActiveChat', {
|
|
data: {
|
|
id: conversation.value.id,
|
|
},
|
|
});
|
|
});
|
|
|
|
useInfiniteScroll(messageListRef, useThrottleFn(fetchMore, 1000), {
|
|
canLoadMore: () => {
|
|
return !isAllLoaded.value;
|
|
},
|
|
distance: 10,
|
|
direction: 'top',
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative h-full">
|
|
<ul
|
|
ref="messageListRef"
|
|
class="px-4 pt-4 flex flex-col-reverse pb-60 bg-n-background overflow-scroll h-full"
|
|
>
|
|
<div
|
|
v-if="isAnyoneTyping"
|
|
id="conversationFooter"
|
|
class="my-2 py-2 flex items-center w-full"
|
|
>
|
|
<div
|
|
class="flex py-2 px-4 shadow-md rounded-full bg-white dark:bg-slate-700 text-n-slate-11 text-xs font-semibold mx-auto items-center gap-1"
|
|
>
|
|
{{ typingUserNames }}
|
|
<TypingIndicator class="text-n-slate-9" />
|
|
</div>
|
|
</div>
|
|
<template v-for="(message, index) in allMessages" :key="message.id">
|
|
<Message
|
|
v-bind="message"
|
|
:is-email-inbox="isAnEmailChannel"
|
|
:group-with-next="shouldGroupWithNext(index, allMessages)"
|
|
:inbox-supports-reply-to="false"
|
|
force-align-to="left"
|
|
/>
|
|
</template>
|
|
<div v-show="isFetching" class="w-full py-4">
|
|
<Snipper class="mx-auto" />
|
|
</div>
|
|
</ul>
|
|
<div class="p-2 w-full bg-white absolute bottom-0">
|
|
<LiteReplyBox />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style>
|
|
/* this will disable elastic scrolling on the y axis */
|
|
* {
|
|
overscroll-behavior-y: none;
|
|
}
|
|
</style>
|