Merge branch 'users-statuses-pinia' into shigusegubu-themes3

This commit is contained in:
Henry Jameson 2026-08-26 18:58:20 +03:00
commit c91e52f18f
11 changed files with 82 additions and 22 deletions

View file

@ -37,9 +37,7 @@ export const piniaPushNotificationsPlugin = ({ store }) => {
if (store.$id === 'interface') {
if (actionName === 'setNotificationPermission') {
permissionGranted = args[0] === 'granted'
} else if (actionName === 'setLoginStatus') {
user = args[0]
} else {
} else if (actionName !== 'onLogin' && actionName !== 'onLogout') {
return
}
} else if (store.$id === 'sync_config') {

View file

@ -3,8 +3,11 @@ import { showDesktopNotification } from '../desktop_notification_utils/desktop_n
import { useUsersStore } from 'src/stores/users.js'
export const maybeShowChatNotification = (chat) => {
// No messages
if (!chat.lastMessage) return
// No unreads to display
if (chat.unread === 0) return
// Don't notify on outgoing message (shouldn't happen with condition above)
if (useUsersStore().currentUser.id === chat.lastMessage.account_id) return
const opts = {

View file

@ -430,7 +430,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
})
resultUserIds.data.forEach((userId) => {
useStatusesStore().wipeUserStatuses(status.user.id)
useStatusesStore().wipeUserStatuses(userId)
// Users are technically never deleted, just deactivated
// so there's no real need to delete them from store.
})

View file

@ -40,8 +40,8 @@ export const useChatsStore = defineStore('chats', {
useStreamingStore().addSubscriber(socket)
},
startFetching() {
this.fetcher = () => promiseInterval(() => this.fetchChats(), 5000)
this.fetcher()
this.fetcher = promiseInterval(() => this.fetchChats(), 5000)
this.fetchChats()
},
stopFetching() {
this.fetcher?.stop()
@ -56,8 +56,6 @@ export const useChatsStore = defineStore('chats', {
},
resetChats() {
this.data = new Map()
this.stopFetching()
this.startFetching()
},
addNewChats(result) {
useUsersStore().addNewUsers({
@ -79,13 +77,15 @@ export const useChatsStore = defineStore('chats', {
updateChat(updatedChat) {
const chat = this.data.get(updatedChat.id)
if (chat) {
const isNewMessage = chat.lastMessage !== updatedChat.lastMessage
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
if (isNewMessage) maybeShowChatNotification(chat)
} else {
this.data.set(updatedChat.id, updatedChat)
maybeShowChatNotification(updatedChat)
}
maybeShowChatNotification(chat ?? updatedChat)
},
deleteChat(id) {
this.data.delete(id)

View file

@ -46,7 +46,7 @@ const timelineFetcher = (timeline, argument, credentials) => {
}
args.withMuted = !hideMutedPosts
if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline)) {
if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline.name)) {
args.replyVisibility = replyVisibility
}

View file

@ -22,17 +22,41 @@ import {
import { isStatusNotification } from 'src/services/notification_utils/notification_utils_sw.js'
export const defaultState = () => ({
// Prevents desktop notification spam on startup
desktopNotificationSilence: true,
// Pagination
maxId: '',
minId: '',
// Order
data: [],
// TODO: Implement!
// Useful for making notification as seen
// when interacting with status
statusNotificationRelations: new WeakMap(),
// ID to Object notification
idStore: new Map(),
statusIdStore: new Set(),
// Reference to WS subscriber
socket: null,
// Indicates whether notifications receive push updates
streaming: false,
// Indicates whether notifications are SUPPOSED to be fetching
// this is partiualrly useful for when pausing/resuming. I.e.
// whether we need to start fetching again if timeline was resumed.
fetching: true,
// Reference to fetcher, used for polling for new notifications
// and manually fetching old notifications
fetcher: null,
// Whether notifications fetcher has been paused - it stops fetching
// (but still receives pushes!)
paused: false,
})

View file

@ -19,8 +19,10 @@ export const useReportsStore = defineStore('reports', {
actions: {
openUserReportingModal({ userId, statusIds = [] }) {
const preTickedIds = new Set(statusIds)
// There shouldn't be a case where this is undefined
const userAllStatusesIds = useStatusesStore().statusesPerUser.get(userId)
// There could be a case (i.e. user is only ever mentioned in someone else's post -> user popover)
// where user has no known posts
const userAllStatusesIds =
useStatusesStore().statusesPerUser.get(userId) ?? new Set()
// Set constructor should take care of duplicated IDs and order,
// later duplicated IDs will be dropped in favor of earlier
const sortedIds = new Set([...preTickedIds, ...userAllStatusesIds])

View file

@ -534,7 +534,7 @@ export const useStatusesStore = defineStore('statuses', {
// For when blocking a user
wipeUserStatuses(userId) {
const removed = this.statusesPerUser.get(userId)
const removed = this.statusesPerUser.get(userId) ?? new Set()
removed.forEach((statusId) => {
const status = this.allStatuses.get(statusId)
this.allStatuses.delete(statusId)

View file

@ -8,18 +8,49 @@ import { TIMELINE_STREAM_MAP, useStreamingStore } from 'src/stores/streaming.js'
const emptyTl = (name, argument = null) => {
const result = {
// Name of the timeline. Useful for debugging and logging
name,
// Order of statuses, important for timelines that
// have different ordering, i.e. bookmarks and favorites
order: [],
// All statuses belonging to the timeline
statusIds: new Set(),
// Statuses shown to user
visibleStatusIds: new Set(),
// Number of statuses not shown yet
newStatusCount: 0,
// Pagination
maxId: '',
minId: '',
// Indicates whether timeline receives push updates
streaming: false,
// Indicates whether timeline is SUPPOSED to be fetching
// this is partiualrly useful for when pausing/resuming
// timeline. I.e. whether we need to start fetching again
// if timeline was resumed.
fetching: false,
// Indicates that in recent poll update we've hit more than or
// equal to 20 statuses and most likely missed some statuses
// between polls
reloadNeeded: false,
// Reference to fetcher, used for polling for new statuses and
// manually fetching old statuses
fetcher: null,
// Reference to WS subscriber
socket: null,
// Whether the timeline has been paused - it stops fetching
// (but still receives pushes!)
paused: false,
}
@ -316,6 +347,14 @@ export const useTimelinesStore = defineStore('timelines', {
reason,
)
return
} else if (timeline.paused) {
console.debug(
'[Timelines] Deactivating paused timeline',
timelineName,
'Reason:',
reason,
)
timeline.fetching = false
} else {
timeline.fetcher.stopFetching()
console.debug(

View file

@ -50,7 +50,6 @@ import { promiseInterval } from 'src/services/promise_interval/promise_interval.
export const useUsersStore = defineStore('users', {
state: () => ({
loggingIn: false,
lastLoginName: null,
currentUser: null,
users: new Map(),
usersByName: new Map(),
@ -616,7 +615,6 @@ export const useUsersStore = defineStore('users', {
user.muteIds = new Set()
user.domainMutes = new Set()
this.lastLoginName = user.screen_name
useTimelinesStore().deactivateAll()
useStatusesStore().resetStatuses()
@ -739,13 +737,13 @@ export const useUsersStore = defineStore('users', {
oauth.clearToken()
this.currentUser = null
this.lastLoginName = null
useNotificationsStore().deactivate()
// Full reset on logout success
useTimelinesStore().deactivateAll()
useStatusesStore().resetStatuses()
useChatsStore().stopFetching()
useChatsStore().resetChats()
this.users = new Map()
@ -777,6 +775,7 @@ export const useUsersStore = defineStore('users', {
useAnnouncementsStore().startFetching()
useListsStore().startFetching()
useBookmarkFoldersStore().startFetching()
useChatsStore().startFetching()
store?.dispatch('startFetchingFollowRequests')
})
.finally(() => {
@ -785,7 +784,4 @@ export const useUsersStore = defineStore('users', {
})
},
},
persist: {
paths: ['lastLoginName'],
},
})

View file

@ -679,7 +679,6 @@ describe('Users store', () => {
expect(store.usersByName).to.have.length(1)
expect(store.usersByURL).to.have.length(1)
expect(store.relationships).to.have.length(0)
expect(store.lastLoginName).to.eql(userScreenName)
spies.forEach((spy, index) => {
expect(spy, `Spy ${index} has failed`).to.have.been.called
})
@ -783,7 +782,6 @@ describe('Users store', () => {
expect(store.loggedIn).to.eql(true)
await store.logout()
expect(store.loggedIn).to.eql(false)
expect(store.lastLoginName).to.eql(null)
expect(revokeApi).to.have.been.called
expect(store.users).to.have.length(0)
expect(store.usersByName).to.have.length(0)