From 5bc802932e11d17a829c062c02867d75951f467c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 8 Jul 2026 20:29:59 +0300 Subject: [PATCH] migrate chats module to pinia --- src/api/chats.js | 5 +- src/components/chat_list/chat_list.js | 11 +- src/components/chat_message/chat_message.js | 6 +- .../chat_message_list/chat_message_list.js | 5 +- src/components/chat_view/chat_view.js | 34 +++-- .../extra_notifications.js | 12 +- .../extra_notifications.vue | 2 +- src/components/mobile_nav/mobile_nav.js | 15 +- src/components/mobile_nav/mobile_nav.vue | 2 +- src/components/nav_panel/nav_panel.js | 5 +- src/components/navigation/navigation.js | 2 +- src/components/notifications/notifications.js | 7 +- src/components/side_drawer/side_drawer.js | 4 +- src/components/side_drawer/side_drawer.vue | 4 +- src/modules/api.js | 3 +- src/modules/chats.js | 141 ------------------ src/modules/index.js | 2 - src/modules/users.js | 3 +- src/services/chat_utils/chat_utils.js | 8 +- .../notification_utils/notification_utils.js | 5 +- src/stores/chats.js | 114 ++++++++++++++ 21 files changed, 192 insertions(+), 198 deletions(-) delete mode 100644 src/modules/chats.js create mode 100644 src/stores/chats.js diff --git a/src/api/chats.js b/src/api/chats.js index 001f00f26..5d766dec4 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -1,6 +1,9 @@ import { paramsString, promisedRequest } from './helpers.js' -import { parseChat, parseChatMessage } from 'src/services/entity_normalizer/entity_normalizer.service.js' +import { + parseChat, + parseChatMessage, +} from 'src/services/entity_normalizer/entity_normalizer.service.js' const PLEROMA_CHATS_URL = '/api/v1/pleroma/chats' const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` diff --git a/src/components/chat_list/chat_list.js b/src/components/chat_list/chat_list.js index 597fcc709..446ccb70a 100644 --- a/src/components/chat_list/chat_list.js +++ b/src/components/chat_list/chat_list.js @@ -1,9 +1,12 @@ -import { mapGetters, mapState } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' +import { mapState } from 'vuex' import ChatListItem from 'src/components/chat_list_item/chat_list_item.vue' import ChatNew from 'src/components/chat_new/chat_new.vue' import List from 'src/components/list/list.vue' +import { useChatsStore } from 'src/stores/chats.js' + const ChatList = { components: { ChatListItem, @@ -14,7 +17,7 @@ const ChatList = { ...mapState({ currentUser: (state) => state.users.currentUser, }), - ...mapGetters(['sortedChatList']), + ...mapPiniaState(useChatsStore, ['sortedChatList']), }, data() { return { @@ -22,12 +25,12 @@ const ChatList = { } }, created() { - this.$store.dispatch('fetchChats', { latest: true }) + useChatsStore().fetchChats() }, methods: { cancelNewChat() { this.isNew = false - this.$store.dispatch('fetchChats', { latest: true }) + useChatsStore().fetchChats() }, newChat() { this.isNew = true diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index 9ec73dc44..bacfd3e94 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -15,7 +15,11 @@ import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { library } from '@fortawesome/fontawesome-svg-core' -import { faEllipsisH, faTimes, faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { + faCircleNotch, + faEllipsisH, + faTimes, +} from '@fortawesome/free-solid-svg-icons' library.add(faTimes, faEllipsisH, faCircleNotch) diff --git a/src/components/chat_message_list/chat_message_list.js b/src/components/chat_message_list/chat_message_list.js index 71199608d..1f709d0a1 100644 --- a/src/components/chat_message_list/chat_message_list.js +++ b/src/components/chat_message_list/chat_message_list.js @@ -1,6 +1,4 @@ -import { orderBy, throttle, uniqueId } from 'lodash' -import { mapState as mapPiniaState } from 'pinia' -import { mapGetters, mapState } from 'vuex' +import { orderBy, uniqueId } from 'lodash' import ChatMessage from 'src/components/chat_message/chat_message.vue' @@ -34,7 +32,6 @@ const ChatMessageList = { const date = new Date(message.created_at) const olderMessage = messages[index - 1] - const newerMessage = messages[index + 1] const newerItem = acc[acc.length - 1] const diff = olderMessage diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index 2df1a0c5e..8cdb3535f 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -1,7 +1,7 @@ -import { throttle } from 'lodash' -import { nextTick } from 'vue' +import { maxBy, minBy, sortBy, throttle } from 'lodash' import { mapState as mapPiniaState } from 'pinia' -import { mapGetters, mapState } from 'vuex' +import { nextTick } from 'vue' +import { mapState } from 'vuex' import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue' import ChatTitle from 'src/components/chat_title/chat_title.vue' @@ -15,16 +15,17 @@ import { isScrollable, } from './chat_layout_utils.js' +import { useChatsStore } from 'src/stores/chats.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useOAuthStore } from 'src/stores/oauth.js' import { chatMessages, + deleteChatMessage, getOrCreateChat, readChat, sendChatMessage, - deleteChatMessage, } from 'src/api/chats.js' import { WSConnectionStatus } from 'src/api/websocket.js' @@ -215,7 +216,7 @@ const Chat = { credentials: useOAuthStore().token, }) - this.$store.commit('readChat', { id: this.chat.id, lastId: lastReadId }) + useChatsStore().readChat(this.chat.id) }, bottomedOut(offset) { return isBottomedOut(offset) @@ -378,7 +379,10 @@ const Chat = { // Sanity check if (message.chat_id !== this.chat.id) { - console.warn(`Chat message doesn't belong to current chat (id: ${this.chat.id})!!`, message) + console.warn( + `Chat message doesn't belong to current chat (id: ${this.chat.id})!!`, + message, + ) return } @@ -386,7 +390,10 @@ const Chat = { if (message.idempotency_key) { if (this.pendingMessagesIndex[message.idempotencyKeyIndex]) { delete this.pendingMessagesIndex[message.idempotencyKeyIndex] - this.pendingMessages = this.pendingMessages.filter(({ idempotency_key }) => idempotency_key !== message.idempotency_key) + this.pendingMessages = this.pendingMessages.filter( + ({ idempotency_key }) => + idempotency_key !== message.idempotency_key, + ) } } @@ -445,7 +452,7 @@ const Chat = { retriesLeft: MAX_RETRIES, }) }, - async doSendMessage({ params, pendingId, retriesLeft = MAX_RETRIES }) { + async doSendMessage({ params, retriesLeft = MAX_RETRIES }) { if (retriesLeft <= 0) return try { @@ -458,7 +465,11 @@ const Chat = { messages: [{ ...data }], }) } catch (error) { - if (error.name !== 'StatusCodeError' || error.message === 'Failed to fetch') throw error + if ( + error.name !== 'StatusCodeError' || + error.message === 'Failed to fetch' + ) + throw error console.error('Error sending message', error) this.handleMessageError({ @@ -471,11 +482,10 @@ const Chat = { (error.statusCode >= 500 && error.statusCode < 600) || error.message === 'Failed to fetch' ) { - this.messageRetriers[fakeMessage.id] = setTimeout( + this.messageRetriers[params.idempotencyKey] = setTimeout( () => { this.doSendMessage({ params, - fakeMessage, retriesLeft: retriesLeft - 1, }) }, @@ -484,7 +494,7 @@ const Chat = { } } }, - handleMessageError(idempotencyKey, isRetry) { + handleMessageError(idempotencyKey, isRetry) { const fakeMessage = this.pendingMessagesIndex[idempotencyKey] if (fakeMessage) { diff --git a/src/components/extra_notifications/extra_notifications.js b/src/components/extra_notifications/extra_notifications.js index 31ca57aaa..24ed074af 100644 --- a/src/components/extra_notifications/extra_notifications.js +++ b/src/components/extra_notifications/extra_notifications.js @@ -1,7 +1,8 @@ -import { mapState as mapPiniaState } from 'pinia' +import { mapState } from 'pinia' import { mapGetters } from 'vuex' import { useAnnouncementsStore } from 'src/stores/announcements.js' +import { useChatsStore } from 'src/stores/chats.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' @@ -21,7 +22,7 @@ const ExtraNotifications = { return ( this.mergedConfig.showExtraNotifications && this.mergedConfig.showChatsInExtraNotifications && - this.unreadChatCount + this.unreadChatsCount ) }, shouldShowAnnouncements() { @@ -53,11 +54,12 @@ const ExtraNotifications = { currentUser() { return this.$store.state.users.currentUser }, - ...mapGetters(['unreadChatCount', 'followRequestCount']), - ...mapPiniaState(useAnnouncementsStore, { + ...mapGetters(['followRequestCount']), + ...mapState(useAnnouncementsStore, { unreadAnnouncementCount: 'unreadAnnouncementCount', }), - ...mapPiniaState(useMergedConfigStore, ['mergedConfig']), + ...mapState(useMergedConfigStore, ['mergedConfig']), + ...mapState(useChatsStore, ['unreadChatsCount']), }, methods: { openNotificationSettings() { diff --git a/src/components/extra_notifications/extra_notifications.vue b/src/components/extra_notifications/extra_notifications.vue index 5728dc98f..fc2516815 100644 --- a/src/components/extra_notifications/extra_notifications.vue +++ b/src/components/extra_notifications/extra_notifications.vue @@ -14,7 +14,7 @@ class="fa-scale-110 icon" icon="comments" /> - {{ $t('notifications.unread_chats', { num: unreadChatCount }, unreadChatCount) }} + {{ $t('notifications.unread_chats', { num: unreadChatsCount }, unreadChatsCount) }}
- new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'), - }), shouldConfirmLogout() { return useMergedConfigStore().mergedConfig.modalOnLogout }, closingDrawerMarksAsSeen() { return useMergedConfigStore().mergedConfig.closingDrawerMarksAsSeen }, - ...mapGetters(['unreadChatCount']), + ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), + ...mapState(useMergedConfigStore, { + pinnedItems: (store) => + new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'), + }), + ...mapState(useChatsStore, ['unreadChatsCount']), }, methods: { toggleMobileSidebar() { diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue index 743b7deb0..8cdb70a1a 100644 --- a/src/components/mobile_nav/mobile_nav.vue +++ b/src/components/mobile_nav/mobile_nav.vue @@ -19,7 +19,7 @@ icon="bars" />
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index ae6264217..5a4a78ec4 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -1,5 +1,5 @@ import { mapState as mapPiniaState } from 'pinia' -import { mapGetters, mapState } from 'vuex' +import { mapState } from 'vuex' import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue' import Checkbox from 'src/components/checkbox/checkbox.vue' @@ -10,6 +10,7 @@ import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue' import { useAnnouncementsStore } from 'src/stores/announcements' +import { useChatsStore } from 'src/stores/chats.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' @@ -131,6 +132,7 @@ const NavPanel = { currentUser: (state) => state.users.currentUser, followRequestCount: (state) => state.api.followRequests.length, }), + ...mapPiniaState(useChatsStore, ['unreadChatsCount']), timelinesItems() { return filterNavigation( Object.entries({ ...TIMELINES }) @@ -162,7 +164,6 @@ const NavPanel = { }, ) }, - ...mapGetters(['unreadChatCount']), }, } diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js index 66fb0d347..39fa2c993 100644 --- a/src/components/navigation/navigation.js +++ b/src/components/navigation/navigation.js @@ -76,7 +76,7 @@ export const ROOT_ITEMS = { icon: 'comments', label: 'nav.chats', badgeStyle: 'notification', - badgeGetter: 'unreadChatCount', + badgeGetter: 'unreadChatsCount', criteria: ['chats'], }, friendRequests: { diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 49349b127..393aef639 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -1,6 +1,5 @@ import { mapState } from 'pinia' import { computed } from 'vue' -import { mapGetters } from 'vuex' import ExtraNotifications from 'src/components/extra_notifications/extra_notifications.vue' import Notification from 'src/components/notification/notification.vue' @@ -16,6 +15,7 @@ import notificationsFetcher from '../../services/notifications_fetcher/notificat import NotificationFilters from './notification_filters.vue' import { useAnnouncementsStore } from 'src/stores/announcements.js' +import { useChatsStore } from 'src/stores/chats.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' @@ -115,13 +115,14 @@ const Notifications = { return countExtraNotifications( this.$store, useMergedConfigStore().mergedConfig, + useChatsStore().unreadChatsCount, useAnnouncementsStore().unreadAnnouncementCount, ) }, unseenCountTitle() { return ( this.unseenNotifications.length + - this.unreadChatCount + + this.unreadChatsCount + this.unreadAnnouncementCount ) }, @@ -160,7 +161,7 @@ const Notifications = { return !this.noExtra }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), - ...mapGetters(['unreadChatCount']), + ...mapState(useChatsStore, ['unreadChatsCount']), }, mounted() { this.scrollerRef = this.$refs.root.closest('.column.-scrollable') diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js index 27e9a5249..2098b3dff 100644 --- a/src/components/side_drawer/side_drawer.js +++ b/src/components/side_drawer/side_drawer.js @@ -12,6 +12,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useShoutStore } from 'src/stores/shout' +import { useChatsStore } from 'src/stores/chats.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -113,7 +114,8 @@ const SideDrawer = { sitename: (store) => store.instanceIdentity.name, hideSitename: (store) => store.instanceIdentity.hideSitename, }), - ...mapGetters(['unreadChatCount', 'draftCount']), + ...mapState(useChatsStore, ['unreadChatsCount']), + ...mapGetters(['draftCount']), }, methods: { toggleDrawer() { diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index c034edf91..e0b7331c6 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -106,10 +106,10 @@ icon="comments" /> {{ $t("nav.chats") }} - {{ unreadChatCount }} + {{ unreadChatsCount }} diff --git a/src/modules/api.js b/src/modules/api.js index 232d1776d..e26336c05 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -2,6 +2,7 @@ import { Socket } from 'phoenix' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' +import { useChatsStore } from 'src/stores/chats.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' @@ -174,7 +175,7 @@ const api = { ) { dispatch('stopFetchingTimeline', { timeline: 'friends' }) dispatch('stopFetchingNotifications') - dispatch('stopFetchingChats') + useChatsStore().stopFetchingChats() } commit('resetRetryMultiplier') commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED) diff --git a/src/modules/chats.js b/src/modules/chats.js deleted file mode 100644 index 1b03a85c5..000000000 --- a/src/modules/chats.js +++ /dev/null @@ -1,141 +0,0 @@ -import { find, omitBy, orderBy, sumBy } from 'lodash' -import { reactive } from 'vue' - -import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' -import { promiseInterval } from '../services/promise_interval/promise_interval.js' - -import { useOAuthStore } from 'src/stores/oauth.js' - -import { chats, deleteChatMessage } from 'src/api/chats.js' - -const emptyChatList = () => ({ - data: [], - idStore: {}, -}) - -const defaultState = { - chatList: emptyChatList(), - chatListFetcher: null, -} - -const getChatById = (state, id) => { - return find(state.chatList.data, { id }) -} - -const sortedChatList = (state) => { - return orderBy(state.chatList.data, ['updated_at'], ['desc']) -} - -const unreadChatCount = (state) => { - return sumBy(state.chatList.data, 'unread') -} - -const chatsModule = { - state: { ...defaultState }, - getters: { - sortedChatList, - unreadChatCount, - }, - actions: { - // Chat list - startFetchingChats({ dispatch, commit }) { - const fetcher = () => dispatch('fetchChats', { latest: true }) - commit('setChatListFetcher', { - fetcher: () => promiseInterval(fetcher, 5000), - }) - }, - stopFetchingChats({ commit }) { - commit('setChatListFetcher', { fetcher: undefined }) - }, - fetchChats({ dispatch, rootState }) { - return chats({ - credentials: useOAuthStore().token, - }).then(({ data }) => { - dispatch('addNewChats', { chats: data }) - return chats - }) - }, - addNewChats(store, { chats }) { - const { commit, dispatch, rootGetters } = store - const newChatMessageSideEffects = (chat) => { - maybeShowChatNotification(store, chat) - } - commit( - 'addNewUsers', - chats.map((k) => k.account).filter((k) => k), - ) - commit('addNewChats', { - dispatch, - chats, - rootGetters, - newChatMessageSideEffects, - }) - }, - }, - mutations: { - setChatListFetcher(state, { fetcher }) { - const prevFetcher = state.chatListFetcher - if (prevFetcher) { - prevFetcher.stop() - } - state.chatListFetcher = fetcher && fetcher() - }, - addNewChats(state, { chats, newChatMessageSideEffects }) { - chats.forEach((updatedChat) => { - const chat = getChatById(state, updatedChat.id) - - if (chat) { - const isNewMessage = - (chat.lastMessage && chat.lastMessage.id) !== - (updatedChat.lastMessage && updatedChat.lastMessage.id) - chat.lastMessage = updatedChat.lastMessage - chat.unread = updatedChat.unread - chat.updated_at = updatedChat.updated_at - if (isNewMessage && chat.unread) { - newChatMessageSideEffects(updatedChat) - } - } else { - state.chatList.data.push(updatedChat) - state.chatList.idStore[updatedChat.id] = updatedChat - } - }) - }, - updateChat(state, { chat: updatedChat }) { - const chat = getChatById(state, updatedChat.id) - if (chat) { - chat.lastMessage = updatedChat.lastMessage - chat.unread = updatedChat.unread - chat.updated_at = updatedChat.updated_at - } - if (!chat) { - state.chatList.data.unshift(updatedChat) - } - state.chatList.idStore[updatedChat.id] = updatedChat - }, - deleteChat(state, { id }) { - state.chats.data = state.chats.data.filter( - (conversation) => conversation.last_status.id !== id, - ) - state.chats.idStore = omitBy( - state.chats.idStore, - (conversation) => conversation.last_status.id === id, - ) - }, - resetChats(state, { commit }) { - state.chatList = emptyChatList() - state.currentChatId = null - commit('setChatListFetcher', { fetcher: undefined }) - }, - setChatsLoading(state, { value }) { - state.chats.loading = value - }, - readChat(state, { id, lastReadId }) { - const chat = getChatById(state, id) - if (chat) { - chat.unread = 0 - } - }, - }, -} - -export default chatsModule diff --git a/src/modules/index.js b/src/modules/index.js index e42260c06..c8f3dce39 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -1,5 +1,4 @@ import api from './api.js' -import chats from './chats.js' import drafts from './drafts.js' import notifications from './notifications.js' import profileConfig from './profileConfig.js' @@ -13,5 +12,4 @@ export default { api, profileConfig, drafts, - chats, } diff --git a/src/modules/users.js b/src/modules/users.js index d6b1adaaa..db3811827 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -21,6 +21,7 @@ import { import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' +import { useChatsStore } from 'src/stores/chats.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' @@ -725,7 +726,7 @@ const users = { store.dispatch('stopFetchingFollowRequests') store.commit('clearNotifications') store.commit('resetStatuses') - store.dispatch('resetChats') + useChatsStore().resetChats() oauth.clearToken() Cookies.remove('__Host-pleroma_key', { path: '/' }) useInterfaceStore().setLastTimeline('public-timeline') diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index 6d3181dd1..ccf91e85c 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -1,10 +1,8 @@ import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' -export const maybeShowChatNotification = (store, chat) => { +export const maybeShowChatNotification = (chat) => { if (!chat.lastMessage) return - if (store.rootState.chats.currentChatId === chat.id && !document.hidden) - return - if (store.rootState.users.currentUser.id === chat.lastMessage.account_id) + if (window.vuex.state.users.currentUser.id === chat.lastMessage.account_id) return const opts = { @@ -21,7 +19,7 @@ export const maybeShowChatNotification = (store, chat) => { opts.image = chat.lastMessage.attachment.preview_url } - showDesktopNotification(store.rootState, opts) + showDesktopNotification(window.vuex.state, opts) } export const buildFakeMessage = ({ diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 37e5e95ad..6cb3dbc19 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -191,6 +191,7 @@ export const prepareNotificationObject = (notification, i18n) => { export const countExtraNotifications = ( store, mergedConfig, + unreadChatsCount, unreadAnnouncementCount, ) => { const rootGetters = store.rootGetters || store.getters @@ -200,9 +201,7 @@ export const countExtraNotifications = ( } return [ - mergedConfig.showChatsInExtraNotifications - ? rootGetters.unreadChatCount - : 0, + mergedConfig.showChatsInExtraNotifications ? unreadChatsCount : 0, mergedConfig.showAnnouncementsInExtraNotifications ? unreadAnnouncementCount : 0, diff --git a/src/stores/chats.js b/src/stores/chats.js new file mode 100644 index 000000000..bc5b7f101 --- /dev/null +++ b/src/stores/chats.js @@ -0,0 +1,114 @@ +import { find, omitBy, orderBy, sumBy } from 'lodash' +import { defineStore } from 'pinia' + +import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' +import { promiseInterval } from '../services/promise_interval/promise_interval.js' + +import { useOAuthStore } from 'src/stores/oauth.js' + +import { chats } from 'src/api/chats.js' + +const emptyChatList = () => ({ + data: [], + idStore: {}, +}) + +const defaultState = { + chatList: emptyChatList(), + chatListFetcher: null, +} + +const getChatById = (state, id) => { + return find(state.chatList.data, { id }) +} + +export const useChatsStore = defineStore('chats', { + state: () => ({ ...defaultState }), + getters: { + sortedChatList(state) { + return orderBy(state.chatList.data, ['updated_at'], ['desc']) + }, + unreadChatsCount(state) { + return sumBy(state.chatList.data, 'unread') + }, + }, + actions: { + startFetchingChats() { + const fetcher = () => this.fetchChats() + this.setChatListFetcher(() => promiseInterval(fetcher, 5000)) + }, + stopFetchingChats() { + this.setChatListFetcher(null) + }, + async fetchChats() { + const { data } = await chats({ + credentials: useOAuthStore().token, + }) + + this.addNewChats(data) + }, + setChatListFetcher(fetcher) { + const prevFetcher = this.chatListFetcher + if (prevFetcher) { + prevFetcher.stop() + } + this.chatListFetcher = fetcher?.() + }, + resetChats() { + this.chatList = emptyChatList() + this.setChatListFetcher(null) + }, + addNewChats(chats) { + window.vuex.commit( + 'addNewUsers', + chats.map((k) => k.account).filter((k) => k), + ) + + chats.forEach((updatedChat) => { + const chat = getChatById(this, updatedChat.id) + + if (chat) { + const isNewMessage = + (chat.lastMessage && chat.lastMessage.id) !== + (updatedChat.lastMessage && updatedChat.lastMessage.id) + chat.lastMessage = updatedChat.lastMessage + chat.unread = updatedChat.unread + chat.updated_at = updatedChat.updated_at + if (isNewMessage && chat.unread) { + maybeShowChatNotification(chat) + } + } else { + this.chatList.data.push(updatedChat) + this.chatList.idStore[updatedChat.id] = updatedChat + } + }) + }, + readChat(id) { + const chat = getChatById(this, id) + if (chat) { + chat.unread = 0 + } + }, + updateChat({ chat: updatedChat }) { + const chat = getChatById(this, updatedChat.id) + if (chat) { + chat.lastMessage = updatedChat.lastMessage + chat.unread = updatedChat.unread + chat.updated_at = updatedChat.updated_at + } + if (!chat) { + this.chatList.data.unshift(updatedChat) + } + this.chatList.idStore[updatedChat.id] = updatedChat + }, + deleteChat(id) { + this.chats.data = this.chats.data.filter( + (conversation) => conversation.last_status.id !== id, + ) + this.chats.idStore = omitBy( + this.chats.idStore, + (conversation) => conversation.last_status.id === id, + ) + }, + }, +})