diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index 3b2a66929..a060bcc4f 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -17,10 +17,7 @@ import { useFunctionGetter, } from 'dashboard/composables/store.js'; -// [VITE] [TODO] We are using vue-virtual-scroll for now, since that seemed the simplest way to migrate -// from the current one. But we should consider using tanstack virtual in the future -// https://tanstack.com/virtual/latest/docs/framework/vue/examples/variable -import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'; +import { Virtualizer } from 'virtua/vue'; import ChatListHeader from './ChatListHeader.vue'; import Dialog from 'dashboard/components-next/dialog/Dialog.vue'; import ConversationFilter from 'next/filter/ConversationFilter.vue'; @@ -29,9 +26,9 @@ import ChatTypeTabs from './widgets/ChatTypeTabs.vue'; import ConversationItem from './ConversationItem.vue'; import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue'; import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue'; -import IntersectionObserver from './IntersectionObserver.vue'; import TeleportWithDirection from 'dashboard/components-next/TeleportWithDirection.vue'; import Spinner from 'dashboard/components-next/spinner/Spinner.vue'; +import IntersectionObserver from 'dashboard/components/IntersectionObserver.vue'; import ConversationResolveAttributesModal from 'dashboard/components-next/ConversationWorkflow/ConversationResolveAttributesModal.vue'; import { useUISettings } from 'dashboard/composables/useUISettings'; @@ -46,7 +43,6 @@ import { useSnakeCase, } from 'dashboard/composables/useTransformKeys'; import { useEmitter } from 'dashboard/composables/emitter'; -import { useEventListener } from '@vueuse/core'; import { useConversationRequiredAttributes } from 'dashboard/composables/useConversationRequiredAttributes'; import { emitter } from 'shared/helpers/mitt'; @@ -70,8 +66,6 @@ import { matchesFilters } from '../store/modules/conversations/helpers/filterHel import { CONVERSATION_EVENTS } from '../helper/AnalyticsHelper/events'; import { ASSIGNEE_TYPE_TAB_PERMISSIONS } from 'dashboard/constants/permissions.js'; -import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'; - const props = defineProps({ conversationInbox: { type: [String, Number], default: 0 }, teamId: { type: [String, Number], default: 0 }, @@ -91,9 +85,9 @@ const store = useStore(); const resolveAttributesModalRef = ref(null); const conversationListRef = ref(null); -const conversationDynamicScroller = ref(null); +const virtualListRef = ref(null); -provide('contextMenuElementTarget', conversationDynamicScroller); +provide('contextMenuElementTarget', virtualListRef); const activeAssigneeTab = ref(wootConstants.ASSIGNEE_TYPE.ME); const activeStatus = ref(wootConstants.STATUS_TYPE.OPEN); @@ -161,12 +155,6 @@ const { const { checkMissingAttributes } = useConversationRequiredAttributes(); // computed -const intersectionObserverOptions = computed(() => { - return { - root: conversationListRef.value, - rootMargin: '100px 0px 100px 0px', - }; -}); const hasAppliedFilters = computed(() => { return appliedFilters.value.length !== 0; @@ -384,18 +372,6 @@ function setFiltersFromUISettings() { function emitConversationLoaded() { emit('conversationLoad'); - // [VITE] removing this since the library has changed - // nextTick(() => { - // // Addressing a known issue in the virtual list library where dynamically added items - // // might not render correctly. This workaround involves a slight manual adjustment - // // to the scroll position, triggering the list to refresh its rendering. - // const virtualList = conversationListRef.value; - // const scrollToOffset = virtualList?.scrollToOffset; - // const currentOffset = virtualList?.getOffset() || 0; - // if (scrollToOffset) { - // scrollToOffset(currentOffset + 1); - // } - // }); } function fetchFilteredConversations(payload) { @@ -607,16 +583,13 @@ function loadMoreConversations() { } } -// Add a method to handle scroll events -function handleScroll() { - const scroller = conversationDynamicScroller.value; - if (scroller && scroller.hasScrollbar) { - const { scrollTop, scrollHeight, clientHeight } = scroller.$el; - if (scrollHeight - (scrollTop + clientHeight) < 100) { - loadMoreConversations(); - } - } -} +// Use IntersectionObserver instead of @scroll since Virtualizer only emits on user scroll. +// If the list doesn’t fill the viewport, loading can stall. +// IntersectionObserver triggers as soon as the sentinel is visible. +const intersectionObserverOptions = computed(() => ({ + root: conversationListRef.value, + rootMargin: '100px 0px 100px 0px', +})); function updateAssigneeTab(selectedTab) { if (activeAssigneeTab.value !== selectedTab) { @@ -822,8 +795,6 @@ useEmitter('fetch_conversation_stats', () => { store.dispatch('conversationStats/get', conversationFilters.value); }); -useEventListener(conversationDynamicScroller, 'scroll', handleScroll); - onMounted(() => { store.dispatch('setChatListFilters', conversationFilters.value); setFiltersFromUISettings(); @@ -977,61 +948,42 @@ watch(conversationFilters, (newVal, oldVal) => { />
- - - - + + +
+ +
+

+ {{ $t('CHAT_LIST.EOF') }} +

+
=16 || 14 >=14.17'} - mitt@2.1.0: - resolution: {integrity: sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==} - mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} @@ -4511,6 +4508,26 @@ packages: videojs-wavesurfer@3.8.0: resolution: {integrity: sha512-qHucCBiEW+4dZ0Zp1k4R1elprUOV+QDw87UDA9QRXtO7GK/MrSdoe/TMFxP9SLnJCiX9xnYdf4OQgrmvJ9UVVw==} + virtua@0.48.6: + resolution: {integrity: sha512-Cl4uMvMV5c9RuOy9zhkFMYwx/V4YLBMYLRSWkO8J46opQZ3P7KMq0CqCVOOAKUckjl/r//D2jWTBGYWzmgtzrQ==} + peerDependencies: + react: '>=16.14.0' + react-dom: '>=16.14.0' + solid-js: '>=1.0' + svelte: '>=5.0' + vue: '>=3.2' + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + vite-node@2.0.1: resolution: {integrity: sha512-nVd6kyhPAql0s+xIVJzuF+RSRH8ZimNrm6U8ZvTA4MXv8CHI17TFaQwRaFiK75YX6XeFqZD4IoAaAfi9OR1XvQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4625,11 +4642,6 @@ packages: vue-letter@0.2.1: resolution: {integrity: sha512-IYWp47XUikjKfEniWYlFxeJFKABZwAE5IEjz866qCBytBr2dzqVDdjoMDpBP//krxkzN/QZYyHe6C09y/IODYg==} - vue-observe-visibility@2.0.0-alpha.1: - resolution: {integrity: sha512-flFbp/gs9pZniXR6fans8smv1kDScJ8RS7rEpMjhVabiKeq7Qz3D9+eGsypncjfIyyU84saU88XZ0zjbD6Gq/g==} - peerDependencies: - vue: ^3.0.0 - vue-resize@2.0.0-alpha.1: resolution: {integrity: sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg==} peerDependencies: @@ -4643,11 +4655,6 @@ packages: vue-upload-component@3.1.17: resolution: {integrity: sha512-1orTC5apoFzBz4ku2HAydpviaAOck+ABc83rGypIK/Bgl+TqhtoWsQOhXqbb7vDv7pKlvRVWwml9PM224HyhkA==} - vue-virtual-scroller@2.0.0-beta.8: - resolution: {integrity: sha512-b8/f5NQ5nIEBRTNi6GcPItE4s7kxNHw2AIHLtDp+2QvqdTjVN0FgONwX9cr53jWRgnu+HRLPaWDOR2JPI5MTfQ==} - peerDependencies: - vue: ^3.2.0 - vue3-click-away@1.2.4: resolution: {integrity: sha512-O9Z2KlvIhJT8OxaFy04eiZE9rc1Mk/bp+70dLok68ko3Kr8AW5dU+j8avSk4GDQu94FllSr4m5ul4BpzlKOw1A==} @@ -8226,8 +8233,6 @@ snapshots: minipass@7.1.2: {} - mitt@2.1.0: {} - mitt@3.0.1: {} mlly@1.8.0: @@ -9574,6 +9579,10 @@ snapshots: video.js: 7.18.1 wavesurfer.js: 7.8.6 + virtua@0.48.6(vue@3.5.12(typescript@5.6.2)): + optionalDependencies: + vue: 3.5.12(typescript@5.6.2) + vite-node@2.0.1(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0): dependencies: cac: 6.7.14 @@ -9692,10 +9701,6 @@ snapshots: dependencies: lettersanitizer: 1.0.6 - vue-observe-visibility@2.0.0-alpha.1(vue@3.5.12(typescript@5.6.2)): - dependencies: - vue: 3.5.12(typescript@5.6.2) - vue-resize@2.0.0-alpha.1(vue@3.5.12(typescript@5.6.2)): dependencies: vue: 3.5.12(typescript@5.6.2) @@ -9707,13 +9712,6 @@ snapshots: vue-upload-component@3.1.17: {} - vue-virtual-scroller@2.0.0-beta.8(vue@3.5.12(typescript@5.6.2)): - dependencies: - mitt: 2.1.0 - vue: 3.5.12(typescript@5.6.2) - vue-observe-visibility: 2.0.0-alpha.1(vue@3.5.12(typescript@5.6.2)) - vue-resize: 2.0.0-alpha.1(vue@3.5.12(typescript@5.6.2)) - vue3-click-away@1.2.4: {} vue@3.5.12(typescript@5.6.2):