migrate chats module to pinia

This commit is contained in:
Henry Jameson 2026-07-08 20:29:59 +03:00
commit 5bc802932e
21 changed files with 192 additions and 198 deletions

View file

@ -1,6 +1,9 @@
import { paramsString, promisedRequest } from './helpers.js' 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_CHATS_URL = '/api/v1/pleroma/chats'
const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}` const PLEROMA_CHAT_URL = (id) => `/api/v1/pleroma/chats/by-account-id/${id}`

View file

@ -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 ChatListItem from 'src/components/chat_list_item/chat_list_item.vue'
import ChatNew from 'src/components/chat_new/chat_new.vue' import ChatNew from 'src/components/chat_new/chat_new.vue'
import List from 'src/components/list/list.vue' import List from 'src/components/list/list.vue'
import { useChatsStore } from 'src/stores/chats.js'
const ChatList = { const ChatList = {
components: { components: {
ChatListItem, ChatListItem,
@ -14,7 +17,7 @@ const ChatList = {
...mapState({ ...mapState({
currentUser: (state) => state.users.currentUser, currentUser: (state) => state.users.currentUser,
}), }),
...mapGetters(['sortedChatList']), ...mapPiniaState(useChatsStore, ['sortedChatList']),
}, },
data() { data() {
return { return {
@ -22,12 +25,12 @@ const ChatList = {
} }
}, },
created() { created() {
this.$store.dispatch('fetchChats', { latest: true }) useChatsStore().fetchChats()
}, },
methods: { methods: {
cancelNewChat() { cancelNewChat() {
this.isNew = false this.isNew = false
this.$store.dispatch('fetchChats', { latest: true }) useChatsStore().fetchChats()
}, },
newChat() { newChat() {
this.isNew = true this.isNew = true

View file

@ -15,7 +15,11 @@ import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { library } from '@fortawesome/fontawesome-svg-core' 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) library.add(faTimes, faEllipsisH, faCircleNotch)

View file

@ -1,6 +1,4 @@
import { orderBy, throttle, uniqueId } from 'lodash' import { orderBy, uniqueId } from 'lodash'
import { mapState as mapPiniaState } from 'pinia'
import { mapGetters, mapState } from 'vuex'
import ChatMessage from 'src/components/chat_message/chat_message.vue' import ChatMessage from 'src/components/chat_message/chat_message.vue'
@ -34,7 +32,6 @@ const ChatMessageList = {
const date = new Date(message.created_at) const date = new Date(message.created_at)
const olderMessage = messages[index - 1] const olderMessage = messages[index - 1]
const newerMessage = messages[index + 1]
const newerItem = acc[acc.length - 1] const newerItem = acc[acc.length - 1]
const diff = olderMessage const diff = olderMessage

View file

@ -1,7 +1,7 @@
import { throttle } from 'lodash' import { maxBy, minBy, sortBy, throttle } from 'lodash'
import { nextTick } from 'vue'
import { mapState as mapPiniaState } from 'pinia' 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 ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
import ChatTitle from 'src/components/chat_title/chat_title.vue' import ChatTitle from 'src/components/chat_title/chat_title.vue'
@ -15,16 +15,17 @@ import {
isScrollable, isScrollable,
} from './chat_layout_utils.js' } from './chat_layout_utils.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
import { import {
chatMessages, chatMessages,
deleteChatMessage,
getOrCreateChat, getOrCreateChat,
readChat, readChat,
sendChatMessage, sendChatMessage,
deleteChatMessage,
} from 'src/api/chats.js' } from 'src/api/chats.js'
import { WSConnectionStatus } from 'src/api/websocket.js' import { WSConnectionStatus } from 'src/api/websocket.js'
@ -215,7 +216,7 @@ const Chat = {
credentials: useOAuthStore().token, credentials: useOAuthStore().token,
}) })
this.$store.commit('readChat', { id: this.chat.id, lastId: lastReadId }) useChatsStore().readChat(this.chat.id)
}, },
bottomedOut(offset) { bottomedOut(offset) {
return isBottomedOut(offset) return isBottomedOut(offset)
@ -378,7 +379,10 @@ const Chat = {
// Sanity check // Sanity check
if (message.chat_id !== this.chat.id) { 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 return
} }
@ -386,7 +390,10 @@ const Chat = {
if (message.idempotency_key) { if (message.idempotency_key) {
if (this.pendingMessagesIndex[message.idempotencyKeyIndex]) { if (this.pendingMessagesIndex[message.idempotencyKeyIndex]) {
delete 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, retriesLeft: MAX_RETRIES,
}) })
}, },
async doSendMessage({ params, pendingId, retriesLeft = MAX_RETRIES }) { async doSendMessage({ params, retriesLeft = MAX_RETRIES }) {
if (retriesLeft <= 0) return if (retriesLeft <= 0) return
try { try {
@ -458,7 +465,11 @@ const Chat = {
messages: [{ ...data }], messages: [{ ...data }],
}) })
} catch (error) { } 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) console.error('Error sending message', error)
this.handleMessageError({ this.handleMessageError({
@ -471,11 +482,10 @@ const Chat = {
(error.statusCode >= 500 && error.statusCode < 600) || (error.statusCode >= 500 && error.statusCode < 600) ||
error.message === 'Failed to fetch' error.message === 'Failed to fetch'
) { ) {
this.messageRetriers[fakeMessage.id] = setTimeout( this.messageRetriers[params.idempotencyKey] = setTimeout(
() => { () => {
this.doSendMessage({ this.doSendMessage({
params, params,
fakeMessage,
retriesLeft: retriesLeft - 1, retriesLeft: retriesLeft - 1,
}) })
}, },
@ -484,7 +494,7 @@ const Chat = {
} }
} }
}, },
handleMessageError(idempotencyKey, isRetry) { handleMessageError(idempotencyKey, isRetry) {
const fakeMessage = this.pendingMessagesIndex[idempotencyKey] const fakeMessage = this.pendingMessagesIndex[idempotencyKey]
if (fakeMessage) { if (fakeMessage) {

View file

@ -1,7 +1,8 @@
import { mapState as mapPiniaState } from 'pinia' import { mapState } from 'pinia'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
@ -21,7 +22,7 @@ const ExtraNotifications = {
return ( return (
this.mergedConfig.showExtraNotifications && this.mergedConfig.showExtraNotifications &&
this.mergedConfig.showChatsInExtraNotifications && this.mergedConfig.showChatsInExtraNotifications &&
this.unreadChatCount this.unreadChatsCount
) )
}, },
shouldShowAnnouncements() { shouldShowAnnouncements() {
@ -53,11 +54,12 @@ const ExtraNotifications = {
currentUser() { currentUser() {
return this.$store.state.users.currentUser return this.$store.state.users.currentUser
}, },
...mapGetters(['unreadChatCount', 'followRequestCount']), ...mapGetters(['followRequestCount']),
...mapPiniaState(useAnnouncementsStore, { ...mapState(useAnnouncementsStore, {
unreadAnnouncementCount: 'unreadAnnouncementCount', unreadAnnouncementCount: 'unreadAnnouncementCount',
}), }),
...mapPiniaState(useMergedConfigStore, ['mergedConfig']), ...mapState(useMergedConfigStore, ['mergedConfig']),
...mapState(useChatsStore, ['unreadChatsCount']),
}, },
methods: { methods: {
openNotificationSettings() { openNotificationSettings() {

View file

@ -14,7 +14,7 @@
class="fa-scale-110 icon" class="fa-scale-110 icon"
icon="comments" icon="comments"
/> />
{{ $t('notifications.unread_chats', { num: unreadChatCount }, unreadChatCount) }} {{ $t('notifications.unread_chats', { num: unreadChatsCount }, unreadChatsCount) }}
</router-link> </router-link>
</div> </div>
<div <div

View file

@ -1,6 +1,5 @@
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent } from 'vue'
import { mapGetters } from 'vuex'
import NavigationPins from 'src/components/navigation/navigation_pins.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
@ -10,6 +9,7 @@ import {
} from '../../services/notification_utils/notification_utils' } from '../../services/notification_utils/notification_utils'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
@ -68,6 +68,7 @@ const MobileNav = {
countExtraNotifications( countExtraNotifications(
this.$store, this.$store,
useMergedConfigStore().mergedConfig, useMergedConfigStore().mergedConfig,
useChatsStore().unreadChatsCount,
useAnnouncementsStore().unreadAnnouncementCount, useAnnouncementsStore().unreadAnnouncementCount,
) )
) )
@ -87,18 +88,18 @@ const MobileNav = {
isChat() { isChat() {
return this.$route.name === 'chat' return this.$route.name === 'chat'
}, },
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapState(useMergedConfigStore, {
pinnedItems: (store) =>
new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'),
}),
shouldConfirmLogout() { shouldConfirmLogout() {
return useMergedConfigStore().mergedConfig.modalOnLogout return useMergedConfigStore().mergedConfig.modalOnLogout
}, },
closingDrawerMarksAsSeen() { closingDrawerMarksAsSeen() {
return useMergedConfigStore().mergedConfig.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: { methods: {
toggleMobileSidebar() { toggleMobileSidebar() {

View file

@ -19,7 +19,7 @@
icon="bars" icon="bars"
/> />
<div <div
v-if="(unreadChatCount && !chatsPinned) || unreadAnnouncementCount" v-if="(unreadChatsCount && !chatsPinned) || unreadAnnouncementCount"
class="badge -dot -notification" class="badge -dot -notification"
/> />
</button> </button>

View file

@ -1,5 +1,5 @@
import { mapState as mapPiniaState } from 'pinia' 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 BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
import Checkbox from 'src/components/checkbox/checkbox.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 NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { useAnnouncementsStore } from 'src/stores/announcements' import { useAnnouncementsStore } from 'src/stores/announcements'
import { useChatsStore } from 'src/stores/chats.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js'
@ -131,6 +132,7 @@ const NavPanel = {
currentUser: (state) => state.users.currentUser, currentUser: (state) => state.users.currentUser,
followRequestCount: (state) => state.api.followRequests.length, followRequestCount: (state) => state.api.followRequests.length,
}), }),
...mapPiniaState(useChatsStore, ['unreadChatsCount']),
timelinesItems() { timelinesItems() {
return filterNavigation( return filterNavigation(
Object.entries({ ...TIMELINES }) Object.entries({ ...TIMELINES })
@ -162,7 +164,6 @@ const NavPanel = {
}, },
) )
}, },
...mapGetters(['unreadChatCount']),
}, },
} }

View file

@ -76,7 +76,7 @@ export const ROOT_ITEMS = {
icon: 'comments', icon: 'comments',
label: 'nav.chats', label: 'nav.chats',
badgeStyle: 'notification', badgeStyle: 'notification',
badgeGetter: 'unreadChatCount', badgeGetter: 'unreadChatsCount',
criteria: ['chats'], criteria: ['chats'],
}, },
friendRequests: { friendRequests: {

View file

@ -1,6 +1,5 @@
import { mapState } from 'pinia' import { mapState } from 'pinia'
import { computed } from 'vue' import { computed } from 'vue'
import { mapGetters } from 'vuex'
import ExtraNotifications from 'src/components/extra_notifications/extra_notifications.vue' import ExtraNotifications from 'src/components/extra_notifications/extra_notifications.vue'
import Notification from 'src/components/notification/notification.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 NotificationFilters from './notification_filters.vue'
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
@ -115,13 +115,14 @@ const Notifications = {
return countExtraNotifications( return countExtraNotifications(
this.$store, this.$store,
useMergedConfigStore().mergedConfig, useMergedConfigStore().mergedConfig,
useChatsStore().unreadChatsCount,
useAnnouncementsStore().unreadAnnouncementCount, useAnnouncementsStore().unreadAnnouncementCount,
) )
}, },
unseenCountTitle() { unseenCountTitle() {
return ( return (
this.unseenNotifications.length + this.unseenNotifications.length +
this.unreadChatCount + this.unreadChatsCount +
this.unreadAnnouncementCount this.unreadAnnouncementCount
) )
}, },
@ -160,7 +161,7 @@ const Notifications = {
return !this.noExtra return !this.noExtra
}, },
...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
...mapGetters(['unreadChatCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
}, },
mounted() { mounted() {
this.scrollerRef = this.$refs.root.closest('.column.-scrollable') this.scrollerRef = this.$refs.root.closest('.column.-scrollable')

View file

@ -12,6 +12,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useShoutStore } from 'src/stores/shout' import { useShoutStore } from 'src/stores/shout'
import { useChatsStore } from 'src/stores/chats.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
import { import {
@ -113,7 +114,8 @@ const SideDrawer = {
sitename: (store) => store.instanceIdentity.name, sitename: (store) => store.instanceIdentity.name,
hideSitename: (store) => store.instanceIdentity.hideSitename, hideSitename: (store) => store.instanceIdentity.hideSitename,
}), }),
...mapGetters(['unreadChatCount', 'draftCount']), ...mapState(useChatsStore, ['unreadChatsCount']),
...mapGetters(['draftCount']),
}, },
methods: { methods: {
toggleDrawer() { toggleDrawer() {

View file

@ -106,10 +106,10 @@
icon="comments" icon="comments"
/> {{ $t("nav.chats") }} /> {{ $t("nav.chats") }}
<span <span
v-if="unreadChatCount" v-if="unreadChatsCount"
class="badge -notification" class="badge -notification"
> >
{{ unreadChatCount }} {{ unreadChatsCount }}
</span> </span>
</router-link> </router-link>
</li> </li>

View file

@ -2,6 +2,7 @@ import { Socket } from 'phoenix'
import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' 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 { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js' import { useInterfaceStore } from 'src/stores/interface.js'
import { useOAuthStore } from 'src/stores/oauth.js' import { useOAuthStore } from 'src/stores/oauth.js'
@ -174,7 +175,7 @@ const api = {
) { ) {
dispatch('stopFetchingTimeline', { timeline: 'friends' }) dispatch('stopFetchingTimeline', { timeline: 'friends' })
dispatch('stopFetchingNotifications') dispatch('stopFetchingNotifications')
dispatch('stopFetchingChats') useChatsStore().stopFetchingChats()
} }
commit('resetRetryMultiplier') commit('resetRetryMultiplier')
commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED) commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)

View file

@ -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

View file

@ -1,5 +1,4 @@
import api from './api.js' import api from './api.js'
import chats from './chats.js'
import drafts from './drafts.js' import drafts from './drafts.js'
import notifications from './notifications.js' import notifications from './notifications.js'
import profileConfig from './profileConfig.js' import profileConfig from './profileConfig.js'
@ -13,5 +12,4 @@ export default {
api, api,
profileConfig, profileConfig,
drafts, drafts,
chats,
} }

View file

@ -21,6 +21,7 @@ import {
import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
import { useChatsStore } from 'src/stores/chats.js'
import { useEmojiStore } from 'src/stores/emoji.js' import { useEmojiStore } from 'src/stores/emoji.js'
import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
@ -725,7 +726,7 @@ const users = {
store.dispatch('stopFetchingFollowRequests') store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications') store.commit('clearNotifications')
store.commit('resetStatuses') store.commit('resetStatuses')
store.dispatch('resetChats') useChatsStore().resetChats()
oauth.clearToken() oauth.clearToken()
Cookies.remove('__Host-pleroma_key', { path: '/' }) Cookies.remove('__Host-pleroma_key', { path: '/' })
useInterfaceStore().setLastTimeline('public-timeline') useInterfaceStore().setLastTimeline('public-timeline')

View file

@ -1,10 +1,8 @@
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js' import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
export const maybeShowChatNotification = (store, chat) => { export const maybeShowChatNotification = (chat) => {
if (!chat.lastMessage) return if (!chat.lastMessage) return
if (store.rootState.chats.currentChatId === chat.id && !document.hidden) if (window.vuex.state.users.currentUser.id === chat.lastMessage.account_id)
return
if (store.rootState.users.currentUser.id === chat.lastMessage.account_id)
return return
const opts = { const opts = {
@ -21,7 +19,7 @@ export const maybeShowChatNotification = (store, chat) => {
opts.image = chat.lastMessage.attachment.preview_url opts.image = chat.lastMessage.attachment.preview_url
} }
showDesktopNotification(store.rootState, opts) showDesktopNotification(window.vuex.state, opts)
} }
export const buildFakeMessage = ({ export const buildFakeMessage = ({

View file

@ -191,6 +191,7 @@ export const prepareNotificationObject = (notification, i18n) => {
export const countExtraNotifications = ( export const countExtraNotifications = (
store, store,
mergedConfig, mergedConfig,
unreadChatsCount,
unreadAnnouncementCount, unreadAnnouncementCount,
) => { ) => {
const rootGetters = store.rootGetters || store.getters const rootGetters = store.rootGetters || store.getters
@ -200,9 +201,7 @@ export const countExtraNotifications = (
} }
return [ return [
mergedConfig.showChatsInExtraNotifications mergedConfig.showChatsInExtraNotifications ? unreadChatsCount : 0,
? rootGetters.unreadChatCount
: 0,
mergedConfig.showAnnouncementsInExtraNotifications mergedConfig.showAnnouncementsInExtraNotifications
? unreadAnnouncementCount ? unreadAnnouncementCount
: 0, : 0,

114
src/stores/chats.js Normal file
View file

@ -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,
)
},
},
})