From ec2937ec1f3b0ae153f79604eb35b57ffe0f9af2 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 13 Nov 2023 17:29:25 +0200
Subject: add options for marking single notification as read

---
 src/components/notification/notification.vue | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/components/notification/notification.vue')

diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 6b3315f9..01ad395f 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -6,6 +6,7 @@
       class="Notification"
       :compact="true"
       :statusoid="notification.status"
+      @interacted="interacted"
     />
   </article>
   <article v-else>
-- 
cgit v1.2.3-70-g09d2


From aad3225d25460170a8dd48f8ffcbc63f99a28b7f Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Nov 2023 19:26:18 +0200
Subject: refactored notifications into their own module separate from statuses
 (WIP)

---
 src/components/notification/notification.vue       |   2 +-
 src/components/notifications/notifications.js      |  16 +--
 src/main.js                                        |   2 +
 src/modules/notifications.js                       | 158 +++++++++++++++++++++
 src/modules/statuses.js                            | 141 +-----------------
 src/modules/users.js                               |   2 +-
 .../desktop_notification_utils.js                  |   2 +-
 .../entity_normalizer/entity_normalizer.service.js |   1 -
 .../notification_utils/notification_utils.js       |   2 +-
 .../notifications_fetcher.service.js               |   2 +-
 10 files changed, 170 insertions(+), 158 deletions(-)
 create mode 100644 src/modules/notifications.js

(limited to 'src/components/notification/notification.vue')

diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 01ad395f..a8eceab0 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -249,7 +249,7 @@
           <StatusContent
             :class="{ faint: !statusExpanded }"
             :compact="!statusExpanded"
-            :status="notification.action"
+            :status="notification.status"
           />
         </template>
       </div>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index 4cbe8093..a1088dff 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -65,7 +65,7 @@ const Notifications = {
       return notificationsFromStore(this.$store)
     },
     error () {
-      return this.$store.state.statuses.notifications.error
+      return this.$store.state.notifications.error
     },
     unseenNotifications () {
       return unseenNotificationsFromStore(this.$store)
@@ -86,7 +86,7 @@ const Notifications = {
       return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
     },
     loading () {
-      return this.$store.state.statuses.notifications.loading
+      return this.$store.state.notifications.loading
     },
     noHeading () {
       const { layoutType } = this.$store.state.interface
@@ -160,17 +160,7 @@ const Notifications = {
       this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
     },
     notificationClicked (notification) {
-      const { type, id, seen } = notification
-      if (!seen) {
-        switch (type) {
-          case 'mention':
-          case 'pleroma:report':
-          case 'follow_request':
-            break
-          default:
-            this.markOneAsSeen(id)
-        }
-      }
+      // const { type, id, seen } = notification
     },
     notificationInteracted (notification) {
       const { id, seen } = notification
diff --git a/src/main.js b/src/main.js
index 0b7c7674..85eb1f4c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -6,6 +6,7 @@ import './lib/event_target_polyfill.js'
 import interfaceModule from './modules/interface.js'
 import instanceModule from './modules/instance.js'
 import statusesModule from './modules/statuses.js'
+import notificationsModule from './modules/notifications.js'
 import listsModule from './modules/lists.js'
 import usersModule from './modules/users.js'
 import apiModule from './modules/api.js'
@@ -78,6 +79,7 @@ const persistedStateOptions = {
       // TODO refactor users/statuses modules, they depend on each other
       users: usersModule,
       statuses: statusesModule,
+      notifications: notificationsModule,
       lists: listsModule,
       api: apiModule,
       config: configModule,
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
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 6e331d16..d6f19589 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -12,15 +12,6 @@ import {
   isArray,
   omitBy
 } 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'
 
 const emptyTl = (userId = 0) => ({
@@ -40,22 +31,12 @@ const emptyTl = (userId = 0) => ({
   flushMarker: 0
 })
 
-const emptyNotifications = () => ({
-  desktopNotificationSilence: true,
-  maxId: 0,
-  minId: Number.POSITIVE_INFINITY,
-  data: [],
-  idStore: {},
-  loading: false
-})
-
 export const defaultState = () => ({
   allStatuses: [],
   scrobblesNextFetch: {},
   allStatusesObject: {},
   conversationsObject: {},
   maxId: 0,
-  notifications: emptyNotifications(),
   favorites: new Set(),
   timelines: {
     mentions: emptyTl(),
@@ -164,9 +145,6 @@ const removeStatusFromGlobalStorage = (state, status) => {
 
   // TODO: Need to remove from allStatusesObject?
 
-  // Remove possible notification
-  remove(state.notifications.data, ({ action: { id } }) => id === status.id)
-
   // Remove from conversation
   const conversationId = status.statusnet_conversation_id
   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 timelineObject = state.timelines[timeline]
   if (userId) {
@@ -400,7 +332,6 @@ const removeStatus = (state, { timeline, userId }) => {
 
 export const mutations = {
   addNewStatuses,
-  addNewNotifications,
   removeStatus,
   showNewStatuses (state, { timeline }) {
     const oldTimeline = (state.timelines[timeline])
@@ -422,9 +353,6 @@ export const mutations = {
     const userId = excludeUserId ? state.timelines[timeline].userId : undefined
     state.timelines[timeline] = emptyTl(userId)
   },
-  clearNotifications (state) {
-    state.notifications = emptyNotifications()
-  },
   setFavorited (state, { status, value }) {
     const newStatus = state.allStatusesObject[status.id]
 
@@ -507,31 +435,6 @@ export const mutations = {
     const newStatus = state.allStatusesObject[id]
     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 }) {
     state.timelines[timeline].flushMarker = id
   },
@@ -615,20 +518,9 @@ const statuses = {
   actions: {
     addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, 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) => {
-        maybeShowNotification(store, notification)
-      }
-      commit('addNewNotifications', { dispatch, notifications, older, rootGetters, newNotificationSideEffects })
-    },
-    setNotificationsLoading ({ rootState, commit }, { value }) {
-      commit('setNotificationsLoading', { value })
-    },
-    setNotificationsSilence ({ rootState, commit }, { value }) {
-      commit('setNotificationsSilence', { value })
+      const deletions = statuses.filter(status => status.type === 'deletion')
+      console.log(deletions)
     },
     fetchStatus ({ rootState, dispatch }, id) {
       return rootState.api.backendInteractor.fetchStatus({ id })
@@ -725,35 +617,6 @@ const statuses = {
     queueFlushAll ({ rootState, commit }) {
       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) {
       Promise.all([
         rootState.api.backendInteractor.fetchFavoritedByUsers({ id }),
diff --git a/src/modules/users.js b/src/modules/users.js
index 79268bc3..6669d623 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -498,7 +498,7 @@ const users = {
       store.commit('addNewUsers', users)
       store.commit('addNewUsers', targetUsers)
 
-      const notificationsObject = store.rootState.statuses.notifications.idStore
+      const notificationsObject = store.rootState.notifications.idStore
       const relevantNotifications = Object.entries(notificationsObject)
         .filter(([k, val]) => notificationIds.includes(k))
         .map(([k, val]) => val)
diff --git a/src/services/desktop_notification_utils/desktop_notification_utils.js b/src/services/desktop_notification_utils/desktop_notification_utils.js
index dbca4173..bde9f18c 100644
--- a/src/services/desktop_notification_utils/desktop_notification_utils.js
+++ b/src/services/desktop_notification_utils/desktop_notification_utils.js
@@ -7,7 +7,7 @@ const state = { failCreateNotif: false }
 
 export const showDesktopNotification = (rootState, desktopNotificationOpts) => {
   if (!('Notification' in window && window.Notification.permission === 'granted')) return
-  if (rootState.statuses.notifications.desktopNotificationSilence) { return }
+  if (rootState.notifications.desktopNotificationSilence) { return }
 
   if (isSWSupported()) {
     swDesktopNotification(desktopNotificationOpts)
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 610ba1ab..85da5223 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -439,7 +439,6 @@ export const parseNotification = (data) => {
     output.type = mastoDict[data.type] || data.type
     output.seen = data.pleroma.is_seen
     output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
-    output.action = output.status // TODO: Refactor, this is unneeded
     output.target = output.type !== 'move'
       ? null
       : parseUser(data.target)
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index 01cbd5f1..2c25000e 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -6,7 +6,7 @@ import FaviconService from 'src/services/favicon_service/favicon_service.js'
 
 let cachedBadgeUrl = null
 
-export const notificationsFromStore = store => store.state.statuses.notifications.data
+export const notificationsFromStore = store => store.state.notifications.data
 
 export const visibleTypes = store => {
   const rootState = store.rootState || store.state
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 6c247210..3c280b74 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -21,7 +21,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
   const args = { credentials }
   const { getters } = store
   const rootState = store.rootState || store.state
-  const timelineData = rootState.statuses.notifications
+  const timelineData = rootState.notifications
   const hideMutedPosts = getters.mergedConfig.hideMutedPosts
 
   args.includeTypes = mastoApiNotificationTypes
-- 
cgit v1.2.3-70-g09d2

