From aad3225d25460170a8dd48f8ffcbc63f99a28b7f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Nov 2023 19:26:18 +0200 Subject: refactored notifications into their own module separate from statuses (WIP) --- src/modules/notifications.js | 158 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/modules/notifications.js (limited to 'src/modules/notifications.js') diff --git a/src/modules/notifications.js b/src/modules/notifications.js new file mode 100644 index 00000000..03f220c7 --- /dev/null +++ b/src/modules/notifications.js @@ -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 -- cgit v1.2.3-70-g09d2 From 6ed2cb8f436bca1e1cd4c40a0a2df0e96fb5d149 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Nov 2023 20:09:16 +0200 Subject: continue refactor --- src/modules/notifications.js | 8 ++------ src/modules/statuses.js | 9 +++------ 2 files changed, 5 insertions(+), 12 deletions(-) (limited to 'src/modules/notifications.js') diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 03f220c7..9b91f291 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -57,9 +57,7 @@ export const notifications = { }, dismissNotification (state, { id }) { state.data = state.data.filter(n => n.id !== id) - }, - dismissNotifications (state, { finder }) { - state.data = state.data.filter(n => finder) + delete state.idStore[id] }, updateNotification (state, { id, updater }) { const notification = find(state.data, n => n.id === id) @@ -88,7 +86,6 @@ export const notifications = { statusNotifications.forEach(notification => { const id = notification.status.id const referenceStatus = rootState.statuses.allStatusesObject[id] - console.log() if (referenceStatus) { notification.status = referenceStatus @@ -108,14 +105,13 @@ export const notifications = { // eslint-disable-next-line no-prototype-builtins if (!state.idStore.hasOwnProperty(notification.id)) { commit('updateNotificationsMinMaxId', notification.id) + commit('addNewNotifications', { notifications: [notification] }) maybeShowNotification(store, notification) } else if (notification.seen) { state.idStore[notification.id].seen = true } }) - - commit('addNewNotifications', { notifications }) }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) diff --git a/src/modules/statuses.js b/src/modules/statuses.js index d6f19589..c4e0bd85 100644 --- a/src/modules/statuses.js +++ b/src/modules/statuses.js @@ -139,11 +139,11 @@ const addStatusToGlobalStorage = (state, data) => { return result } +// XXX: this isn't actually really used anymore since deletes just don't appear outside streaming, thanks masto // Remove status from the global storages (arrays and objects maintaining statuses) except timelines const removeStatusFromGlobalStorage = (state, status) => { remove(state.allStatuses, { id: status.id }) - - // TODO: Need to remove from allStatusesObject? + delete state.allStatusesObject[status.id] // Remove from conversation const conversationId = status.statusnet_conversation_id @@ -516,11 +516,8 @@ export const mutations = { const statuses = { state: defaultState(), actions: { - addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) { + addNewStatuses ({ rootState, commit, dispatch, state }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId, pagination }) { commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId, pagination }) - - const deletions = statuses.filter(status => status.type === 'deletion') - console.log(deletions) }, fetchStatus ({ rootState, dispatch }, id) { return rootState.api.backendInteractor.fetchStatus({ id }) -- cgit v1.2.3-70-g09d2 From a17defc5abfe60b6aa0dc3275dac2cbec507472a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Nov 2023 20:41:41 +0200 Subject: handle desktop notifications clicks --- src/boot/after_store.js | 2 +- src/components/notifications/notifications.js | 12 +++++++----- src/modules/notifications.js | 15 +++++++++++++++ src/services/sw/sw.js | 10 ++++++++-- 4 files changed, 31 insertions(+), 8 deletions(-) (limited to 'src/modules/notifications.js') diff --git a/src/boot/after_store.js b/src/boot/after_store.js index 6489ef87..84fea954 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -345,7 +345,7 @@ const afterStoreSetup = async ({ store, i18n }) => { store.dispatch('setLayoutHeight', windowHeight()) FaviconService.initFaviconService() - initServiceWorker() + initServiceWorker(store) window.addEventListener('focus', () => updateFocus()) diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index a1088dff..a210e19d 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -159,14 +159,16 @@ const Notifications = { updateScrollPosition () { this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop }, + /* "Interacted" really refers to "actionable" notifications that require user input, + * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear + * the "seen" status upon any clicks on them + */ notificationClicked (notification) { - // const { type, id, seen } = notification + const { id } = notification + this.$store.dispatch('notificationClicked', { id }) }, notificationInteracted (notification) { - const { id, seen } = notification - if (!seen) this.markOneAsSeen(id) - }, - markOneAsSeen (id) { + const { id } = notification this.$store.dispatch('markSingleNotificationAsSeen', { id }) }, markAsSeen () { diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 9b91f291..febff488 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -113,6 +113,21 @@ export const notifications = { } }) }, + notificationClicked ({ state, commit }, id) { + const notification = state.idStore[id] + const { type, seen } = notification + + if (!seen) { + switch (type) { + case 'mention': + case 'pleroma:report': + case 'follow_request': + break + default: + commit('markSingleNotificationAsSeen', { id }) + } + } + }, setNotificationsLoading ({ rootState, commit }, { value }) { commit('setNotificationsLoading', { value }) }, diff --git a/src/services/sw/sw.js b/src/services/sw/sw.js index d4ae8f4f..e28f9168 100644 --- a/src/services/sw/sw.js +++ b/src/services/sw/sw.js @@ -82,12 +82,18 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility) return responseData }) } -export async function initServiceWorker () { +export async function initServiceWorker (store) { if (!isSWSupported()) return await getOrCreateServiceWorker() navigator.serviceWorker.addEventListener('message', (event) => { + const { dispatch } = store console.log('SW MESSAGE', event) - // TODO actually act upon click (open drawer on mobile, open chat/thread etc) + const { type, ...rest } = event + + switch (type) { + case 'notificationClicked': + dispatch('notificationClicked', { id: rest.id }) + } }) } -- cgit v1.2.3-70-g09d2 From af27e2ca7ba87bb9c4b74ea0adee252afd2caabf Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Nov 2023 21:55:01 +0200 Subject: fix typo --- src/modules/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/modules/notifications.js') diff --git a/src/modules/notifications.js b/src/modules/notifications.js index febff488..a3cf76ef 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -113,7 +113,7 @@ export const notifications = { } }) }, - notificationClicked ({ state, commit }, id) { + notificationClicked ({ state, commit }, { id }) { const notification = state.idStore[id] const { type, seen } = notification -- cgit v1.2.3-70-g09d2 From c216340001f1b019b58a075c173b78ff9f4dafa7 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Thu, 16 Nov 2023 22:08:51 +0200 Subject: use dispatch instead of commmit, fix bad copypasta --- src/modules/notifications.js | 8 ++++---- src/services/notification_utils/notification_utils.js | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'src/modules/notifications.js') diff --git a/src/modules/notifications.js b/src/modules/notifications.js index a3cf76ef..9bd38220 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -52,7 +52,7 @@ export const notifications = { }) }, markSingleNotificationAsSeen (state, { id }) { - const notification = find(state.data, n => n.id === id) + const notification = state.idStore[id] if (notification) notification.seen = true }, dismissNotification (state, { id }) { @@ -60,7 +60,7 @@ export const notifications = { delete state.idStore[id] }, updateNotification (state, { id, updater }) { - const notification = find(state.data, n => n.id === id) + const notification = state.idStore[id] notification && updater(notification) } }, @@ -113,7 +113,7 @@ export const notifications = { } }) }, - notificationClicked ({ state, commit }, { id }) { + notificationClicked ({ state, dispatch }, { id }) { const notification = state.idStore[id] const { type, seen } = notification @@ -124,7 +124,7 @@ export const notifications = { case 'follow_request': break default: - commit('markSingleNotificationAsSeen', { id }) + dispatch('markSingleNotificationAsSeen', { id }) } } }, diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 675e744e..342fe6ef 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -82,7 +82,11 @@ export const unseenNotificationsFromStore = store => export const prepareNotificationObject = (notification, i18n) => { if (cachedBadgeUrl === null) { const favicon = FaviconService.getOriginalFavicons()[0] - cachedBadgeUrl = favicon.favimg.href + if (!favicon) { + cachedBadgeUrl = 'about:blank' + } else { + cachedBadgeUrl = favicon.favimg.href + } } const notifObj = { -- cgit v1.2.3-70-g09d2 From a564fc1a1f91faf2a418fd6affadbd42c98825d8 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Sun, 19 Nov 2023 13:54:26 +0200 Subject: consistentcy and bugfix --- src/modules/notifications.js | 2 +- src/services/desktop_notification_utils/desktop_notification_utils.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/modules/notifications.js') diff --git a/src/modules/notifications.js b/src/modules/notifications.js index 9bd38220..c1f5a63e 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -150,7 +150,7 @@ export const notifications = { id, credentials: rootState.users.currentUser.credentials }).then(() => { - closeDesktopNotification(rootState, id) + closeDesktopNotification(rootState, { id }) }) }, dismissNotificationLocal ({ rootState, commit }, { id }) { diff --git a/src/services/desktop_notification_utils/desktop_notification_utils.js b/src/services/desktop_notification_utils/desktop_notification_utils.js index bde9f18c..80b8c6e0 100644 --- a/src/services/desktop_notification_utils/desktop_notification_utils.js +++ b/src/services/desktop_notification_utils/desktop_notification_utils.js @@ -21,7 +21,7 @@ export const showDesktopNotification = (rootState, desktopNotificationOpts) => { } } -export const closeDesktopNotification = (rootState, id) => { +export const closeDesktopNotification = (rootState, { id }) => { if (!('Notification' in window && window.Notification.permission === 'granted')) return if (isSWSupported()) { @@ -33,6 +33,6 @@ export const closeAllDesktopNotifications = (rootState) => { if (!('Notification' in window && window.Notification.permission === 'granted')) return if (isSWSupported()) { - swCloseDesktopNotification() + swCloseDesktopNotification({}) } } -- cgit v1.2.3-70-g09d2