refactored notifications into their own module separate from statuses (WIP)

This commit is contained in:
Henry Jameson 2023-11-16 19:26:18 +02:00
parent fffa7a4f4a
commit aad3225d25
10 changed files with 170 additions and 158 deletions

View File

@ -249,7 +249,7 @@
<StatusContent <StatusContent
:class="{ faint: !statusExpanded }" :class="{ faint: !statusExpanded }"
:compact="!statusExpanded" :compact="!statusExpanded"
:status="notification.action" :status="notification.status"
/> />
</template> </template>
</div> </div>

View File

@ -65,7 +65,7 @@ const Notifications = {
return notificationsFromStore(this.$store) return notificationsFromStore(this.$store)
}, },
error () { error () {
return this.$store.state.statuses.notifications.error return this.$store.state.notifications.error
}, },
unseenNotifications () { unseenNotifications () {
return unseenNotificationsFromStore(this.$store) return unseenNotificationsFromStore(this.$store)
@ -86,7 +86,7 @@ const Notifications = {
return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
}, },
loading () { loading () {
return this.$store.state.statuses.notifications.loading return this.$store.state.notifications.loading
}, },
noHeading () { noHeading () {
const { layoutType } = this.$store.state.interface const { layoutType } = this.$store.state.interface
@ -160,17 +160,7 @@ const Notifications = {
this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
}, },
notificationClicked (notification) { notificationClicked (notification) {
const { type, id, seen } = notification // const { type, id, seen } = notification
if (!seen) {
switch (type) {
case 'mention':
case 'pleroma:report':
case 'follow_request':
break
default:
this.markOneAsSeen(id)
}
}
}, },
notificationInteracted (notification) { notificationInteracted (notification) {
const { id, seen } = notification const { id, seen } = notification

View File

@ -6,6 +6,7 @@ import './lib/event_target_polyfill.js'
import interfaceModule from './modules/interface.js' import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js' import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js' import statusesModule from './modules/statuses.js'
import notificationsModule from './modules/notifications.js'
import listsModule from './modules/lists.js' import listsModule from './modules/lists.js'
import usersModule from './modules/users.js' import usersModule from './modules/users.js'
import apiModule from './modules/api.js' import apiModule from './modules/api.js'
@ -78,6 +79,7 @@ const persistedStateOptions = {
// TODO refactor users/statuses modules, they depend on each other // TODO refactor users/statuses modules, they depend on each other
users: usersModule, users: usersModule,
statuses: statusesModule, statuses: statusesModule,
notifications: notificationsModule,
lists: listsModule, lists: listsModule,
api: apiModule, api: apiModule,
config: configModule, config: configModule,

View File

@ -0,0 +1,158 @@
import apiService from '../services/api/api.service.js'
import {
isStatusNotification,
isValidNotification,
maybeShowNotification
} from '../services/notification_utils/notification_utils.js'
import {
closeDesktopNotification,
closeAllDesktopNotifications
} from '../services/desktop_notification_utils/desktop_notification_utils.js'
const emptyNotifications = () => ({
desktopNotificationSilence: true,
maxId: 0,
minId: Number.POSITIVE_INFINITY,
data: [],
idStore: {},
loading: false
})
export const defaultState = () => ({
...emptyNotifications()
})
export const notifications = {
state: defaultState(),
mutations: {
addNewNotifications (state, { notifications }) {
notifications.forEach(notification => {
state.data.push(notification)
state.idStore[notification.id] = notification
})
},
clearNotifications (state) {
state = emptyNotifications()
},
updateNotificationsMinMaxId (state, id) {
state.maxId = id > state.maxId ? id : state.maxId
state.minId = id < state.minId ? id : state.minId
},
setNotificationsLoading (state, { value }) {
state.loading = value
},
setNotificationsSilence (state, { value }) {
state.desktopNotificationSilence = value
},
markNotificationsAsSeen (state) {
state.data.forEach((notification) => {
notification.seen = true
})
},
markSingleNotificationAsSeen (state, { id }) {
const notification = find(state.data, n => n.id === id)
if (notification) notification.seen = true
},
dismissNotification (state, { id }) {
state.data = state.data.filter(n => n.id !== id)
},
dismissNotifications (state, { finder }) {
state.data = state.data.filter(n => finder)
},
updateNotification (state, { id, updater }) {
const notification = find(state.data, n => n.id === id)
notification && updater(notification)
}
},
actions: {
addNewNotifications (store, { notifications, older }) {
const { commit, dispatch, state, rootState } = store
const validNotifications = notifications.filter((notification) => {
// If invalid notification, update ids but don't add it to store
if (!isValidNotification(notification)) {
console.error('Invalid notification:', notification)
commit('updateNotificationsMinMaxId', notification.id)
return false
}
return true
})
const statusNotifications = validNotifications.filter(notification => isStatusNotification(notification.type) && notification.status)
// Synchronous commit to add all the statuses
commit('addNewStatuses', { statuses: statusNotifications.map(notification => notification.status) })
// Update references to statuses in notifications to ones in the store
statusNotifications.forEach(notification => {
const id = notification.status.id
const referenceStatus = rootState.statuses.allStatusesObject[id]
console.log()
if (referenceStatus) {
notification.status = referenceStatus
}
})
validNotifications.forEach(notification => {
if (notification.type === 'pleroma:report') {
dispatch('addReport', notification.report)
}
if (notification.type === 'pleroma:emoji_reaction') {
dispatch('fetchEmojiReactionsBy', notification.status.id)
}
// Only add a new notification if we don't have one for the same action
// eslint-disable-next-line no-prototype-builtins
if (!state.idStore.hasOwnProperty(notification.id)) {
commit('updateNotificationsMinMaxId', notification.id)
maybeShowNotification(store, notification)
} else if (notification.seen) {
state.idStore[notification.id].seen = true
}
})
commit('addNewNotifications', { notifications })
},
setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value })
},
setNotificationsSilence ({ rootState, commit }, { value }) {
commit('setNotificationsSilence', { value })
},
markNotificationsAsSeen ({ rootState, state, commit }) {
commit('markNotificationsAsSeen')
apiService.markNotificationsAsSeen({
id: state.maxId,
credentials: rootState.users.currentUser.credentials
}).then(() => {
closeAllDesktopNotifications(rootState)
})
},
markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
commit('markSingleNotificationAsSeen', { id })
apiService.markNotificationsAsSeen({
single: true,
id,
credentials: rootState.users.currentUser.credentials
}).then(() => {
closeDesktopNotification(rootState, id)
})
},
dismissNotificationLocal ({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
},
dismissNotification ({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
rootState.api.backendInteractor.dismissNotification({ id })
},
updateNotification ({ rootState, commit }, { id, updater }) {
commit('updateNotification', { id, updater })
}
}
}
export default notifications

View File

@ -12,15 +12,6 @@ import {
isArray, isArray,
omitBy omitBy
} from 'lodash' } from 'lodash'
import {
isStatusNotification,
isValidNotification,
maybeShowNotification
} from '../services/notification_utils/notification_utils.js'
import {
closeDesktopNotification,
closeAllDesktopNotifications
} from '../services/desktop_notification_utils/desktop_notification_utils.js'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
const emptyTl = (userId = 0) => ({ const emptyTl = (userId = 0) => ({
@ -40,22 +31,12 @@ const emptyTl = (userId = 0) => ({
flushMarker: 0 flushMarker: 0
}) })
const emptyNotifications = () => ({
desktopNotificationSilence: true,
maxId: 0,
minId: Number.POSITIVE_INFINITY,
data: [],
idStore: {},
loading: false
})
export const defaultState = () => ({ export const defaultState = () => ({
allStatuses: [], allStatuses: [],
scrobblesNextFetch: {}, scrobblesNextFetch: {},
allStatusesObject: {}, allStatusesObject: {},
conversationsObject: {}, conversationsObject: {},
maxId: 0, maxId: 0,
notifications: emptyNotifications(),
favorites: new Set(), favorites: new Set(),
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
@ -164,9 +145,6 @@ const removeStatusFromGlobalStorage = (state, status) => {
// TODO: Need to remove from allStatusesObject? // TODO: Need to remove from allStatusesObject?
// Remove possible notification
remove(state.notifications.data, ({ action: { id } }) => id === status.id)
// Remove from conversation // Remove from conversation
const conversationId = status.statusnet_conversation_id const conversationId = status.statusnet_conversation_id
if (state.conversationsObject[conversationId]) { if (state.conversationsObject[conversationId]) {
@ -342,52 +320,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
} }
} }
const updateNotificationsMinMaxId = (state, notification) => {
state.notifications.maxId = notification.id > state.notifications.maxId
? notification.id
: state.notifications.maxId
state.notifications.minId = notification.id < state.notifications.minId
? notification.id
: state.notifications.minId
}
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters, newNotificationSideEffects }) => {
each(notifications, (notification) => {
// If invalid notification, update ids but don't add it to store
if (!isValidNotification(notification)) {
console.error('Invalid notification:', notification)
updateNotificationsMinMaxId(state, notification)
return
}
if (isStatusNotification(notification.type)) {
notification.action = addStatusToGlobalStorage(state, notification.action).item
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
}
if (notification.type === 'pleroma:report') {
dispatch('addReport', notification.report)
}
if (notification.type === 'pleroma:emoji_reaction') {
dispatch('fetchEmojiReactionsBy', notification.status.id)
}
// Only add a new notification if we don't have one for the same action
// eslint-disable-next-line no-prototype-builtins
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
updateNotificationsMinMaxId(state, notification)
state.notifications.data.push(notification)
state.notifications.idStore[notification.id] = notification
newNotificationSideEffects(notification)
} else if (notification.seen) {
state.notifications.idStore[notification.id].seen = true
}
})
}
const removeStatus = (state, { timeline, userId }) => { const removeStatus = (state, { timeline, userId }) => {
const timelineObject = state.timelines[timeline] const timelineObject = state.timelines[timeline]
if (userId) { if (userId) {
@ -400,7 +332,6 @@ const removeStatus = (state, { timeline, userId }) => {
export const mutations = { export const mutations = {
addNewStatuses, addNewStatuses,
addNewNotifications,
removeStatus, removeStatus,
showNewStatuses (state, { timeline }) { showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline]) const oldTimeline = (state.timelines[timeline])
@ -422,9 +353,6 @@ export const mutations = {
const userId = excludeUserId ? state.timelines[timeline].userId : undefined const userId = excludeUserId ? state.timelines[timeline].userId : undefined
state.timelines[timeline] = emptyTl(userId) state.timelines[timeline] = emptyTl(userId)
}, },
clearNotifications (state) {
state.notifications = emptyNotifications()
},
setFavorited (state, { status, value }) { setFavorited (state, { status, value }) {
const newStatus = state.allStatusesObject[status.id] const newStatus = state.allStatusesObject[status.id]
@ -507,31 +435,6 @@ export const mutations = {
const newStatus = state.allStatusesObject[id] const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw newStatus.nsfw = nsfw
}, },
setNotificationsLoading (state, { value }) {
state.notifications.loading = value
},
setNotificationsSilence (state, { value }) {
state.notifications.desktopNotificationSilence = value
},
markNotificationsAsSeen (state) {
each(state.notifications.data, (notification) => {
notification.seen = true
})
},
markSingleNotificationAsSeen (state, { id }) {
const notification = find(state.notifications.data, n => n.id === id)
if (notification) notification.seen = true
},
dismissNotification (state, { id }) {
state.notifications.data = state.notifications.data.filter(n => n.id !== id)
},
dismissNotifications (state, { finder }) {
state.notifications.data = state.notifications.data.filter(n => finder)
},
updateNotification (state, { id, updater }) {
const notification = find(state.notifications.data, n => n.id === id)
notification && updater(notification)
},
queueFlush (state, { timeline, id }) { queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id state.timelines[timeline].flushMarker = id
}, },
@ -615,20 +518,9 @@ const statuses = {
actions: { actions: {
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) { addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) {
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination }) commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination })
},
addNewNotifications (store, { notifications, older }) {
const { commit, dispatch, rootGetters } = store
const newNotificationSideEffects = (notification) => { const deletions = statuses.filter(status => status.type === 'deletion')
maybeShowNotification(store, notification) console.log(deletions)
}
commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects })
},
setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value })
},
setNotificationsSilence ({ rootState, commit }, { value }) {
commit('setNotificationsSilence', { value })
}, },
fetchStatus ({ rootState, dispatch }, id) { fetchStatus ({ rootState, dispatch }, id) {
return rootState.api.backendInteractor.fetchStatus({ id }) return rootState.api.backendInteractor.fetchStatus({ id })
@ -725,35 +617,6 @@ const statuses = {
queueFlushAll ({ rootState, commit }) { queueFlushAll ({ rootState, commit }) {
commit('queueFlushAll') commit('queueFlushAll')
}, },
markNotificationsAsSeen ({ rootState, commit }) {
commit('markNotificationsAsSeen')
apiService.markNotificationsAsSeen({
id: rootState.statuses.notifications.maxId,
credentials: rootState.users.currentUser.credentials
}).then(() => {
closeAllDesktopNotifications(rootState)
})
},
markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
commit('markSingleNotificationAsSeen', { id })
apiService.markNotificationsAsSeen({
single: true,
id,
credentials: rootState.users.currentUser.credentials
}).then(() => {
closeDesktopNotification(rootState, id)
})
},
dismissNotificationLocal ({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
},
dismissNotification ({ rootState, commit }, { id }) {
commit('dismissNotification', { id })
rootState.api.backendInteractor.dismissNotification({ id })
},
updateNotification ({ rootState, commit }, { id, updater }) {
commit('updateNotification', { id, updater })
},
fetchFavsAndRepeats ({ rootState, commit }, id) { fetchFavsAndRepeats ({ rootState, commit }, id) {
Promise.all([ Promise.all([
rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),

View File

@ -498,7 +498,7 @@ const users = {
store.commit('addNewUsers', users) store.commit('addNewUsers', users)
store.commit('addNewUsers', targetUsers) store.commit('addNewUsers', targetUsers)
const notificationsObject = store.rootState.statuses.notifications.idStore const notificationsObject = store.rootState.notifications.idStore
const relevantNotifications = Object.entries(notificationsObject) const relevantNotifications = Object.entries(notificationsObject)
.filter(([k, val]) => notificationIds.includes(k)) .filter(([k, val]) => notificationIds.includes(k))
.map(([k, val]) => val) .map(([k, val]) => val)

View File

@ -7,7 +7,7 @@ const state = { failCreateNotif: false }
export const showDesktopNotification = (rootState, desktopNotificationOpts) => { export const showDesktopNotification = (rootState, desktopNotificationOpts) => {
if (!('Notification' in window && window.Notification.permission === 'granted')) return if (!('Notification' in window && window.Notification.permission === 'granted')) return
if (rootState.statuses.notifications.desktopNotificationSilence) { return } if (rootState.notifications.desktopNotificationSilence) { return }
if (isSWSupported()) { if (isSWSupported()) {
swDesktopNotification(desktopNotificationOpts) swDesktopNotification(desktopNotificationOpts)

View File

@ -439,7 +439,6 @@ export const parseNotification = (data) => {
output.type = mastoDict[data.type] || data.type output.type = mastoDict[data.type] || data.type
output.seen = data.pleroma.is_seen output.seen = data.pleroma.is_seen
output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
output.action = output.status // TODO: Refactor, this is unneeded
output.target = output.type !== 'move' output.target = output.type !== 'move'
? null ? null
: parseUser(data.target) : parseUser(data.target)

View File

@ -6,7 +6,7 @@ import FaviconService from 'src/services/favicon_service/favicon_service.js'
let cachedBadgeUrl = null let cachedBadgeUrl = null
export const notificationsFromStore = store => store.state.statuses.notifications.data export const notificationsFromStore = store => store.state.notifications.data
export const visibleTypes = store => { export const visibleTypes = store => {
const rootState = store.rootState || store.state const rootState = store.rootState || store.state

View File

@ -21,7 +21,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const args = { credentials } const args = { credentials }
const { getters } = store const { getters } = store
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const timelineData = rootState.statuses.notifications const timelineData = rootState.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts const hideMutedPosts = getters.mergedConfig.hideMutedPosts
args.includeTypes = mastoApiNotificationTypes args.includeTypes = mastoApiNotificationTypes