commit: f9254e5fb77f5dfb3bbc9021618cfeeb09248fa1
parent e3ca5b0a324d2c627d90f002e39e549caf93dce7
Author: Sean King <seanking2919@protonmail.com>
Date:   Thu,  6 Apr 2023 16:32:21 -0600
Move announcements module to store
Diffstat:
13 files changed, 159 insertions(+), 152 deletions(-)
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
@@ -19,6 +19,7 @@ import FaviconService from '../services/favicon_service/favicon_service.js'
 
 import { useI18nStore } from '../stores/i18n'
 import { useInterfaceStore } from '../stores/interface'
+import { useAnnouncementsStore } from '../stores/announcements'
 
 let staticInitialResults = null
 
@@ -389,7 +390,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
 
   // Start fetching things that don't need to block the UI
   store.dispatch('fetchMutes')
-  store.dispatch('startFetchingAnnouncements')
+  useAnnouncementsStore().startFetchingAnnouncements()
   getTOS({ store })
   getStickers({ store })
 
diff --git a/src/components/announcement/announcement.js b/src/components/announcement/announcement.js
@@ -2,6 +2,7 @@ import { mapState } from 'vuex'
 import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
 import RichContent from '../rich_content/rich_content.jsx'
 import localeService from '../../services/locale/locale.service.js'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 const Announcement = {
   components: {
@@ -67,11 +68,11 @@ const Announcement = {
   methods: {
     markAsRead () {
       if (!this.isRead) {
-        return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
+        return useAnnouncementsStore().markAnnouncementAsRead(this.announcement.id)
       }
     },
     deleteAnnouncement () {
-      return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
+      return useAnnouncementsStore().deleteAnnouncement(this.announcement.id)
     },
     formatTimeOrDate (time, locale) {
       const d = new Date(time)
@@ -85,7 +86,7 @@ const Announcement = {
       this.editing = true
     },
     submitEdit () {
-      this.$store.dispatch('editAnnouncement', {
+      useAnnouncementsStore().editAnnouncement({
         id: this.announcement.id,
         ...this.editedAnnouncement
       })
diff --git a/src/components/announcements_page/announcements_page.js b/src/components/announcements_page/announcements_page.js
@@ -1,6 +1,7 @@
 import { mapState } from 'vuex'
 import Announcement from '../announcement/announcement.vue'
 import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 const AnnouncementsPage = {
   components: {
@@ -20,14 +21,14 @@ const AnnouncementsPage = {
     }
   },
   mounted () {
-    this.$store.dispatch('fetchAnnouncements')
+    useAnnouncementsStore().fetchAnnouncements()
   },
   computed: {
     ...mapState({
       currentUser: state => state.users.currentUser
     }),
     announcements () {
-      return this.$store.state.announcements.announcements
+      return useAnnouncementsStore().announcements
     },
     canPostAnnouncement () {
       return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
@@ -36,7 +37,7 @@ const AnnouncementsPage = {
   methods: {
     postAnnouncement () {
       this.posting = true
-      this.$store.dispatch('postAnnouncement', this.newAnnouncement)
+      useAnnouncementsStore().postAnnouncement(this.newAnnouncement)
         .then(() => {
           this.newAnnouncement.content = ''
           this.startsAt = undefined
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
@@ -5,6 +5,7 @@ import { unseenNotificationsFromStore } from '../../services/notification_utils/
 import GestureService from '../../services/gesture_service/gesture_service'
 import NavigationPins from 'src/components/navigation/navigation_pins.vue'
 import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import {
   faTimes,
@@ -13,6 +14,7 @@ import {
   faArrowUp,
   faMinus
 } from '@fortawesome/free-solid-svg-icons'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 library.add(
   faTimes,
@@ -57,7 +59,8 @@ const MobileNav = {
     isChat () {
       return this.$route.name === 'chat'
     },
-    ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']),
+    ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
+    ...mapGetters(['unreadChatCount']),
     chatsPinned () {
       return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
     },
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
@@ -1,5 +1,6 @@
 import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
 import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
 import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
 import { filterNavigation } from 'src/components/navigation/filter.js'
 import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
@@ -21,6 +22,7 @@ import {
   faList,
   faBullhorn
 } from '@fortawesome/free-solid-svg-icons'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 library.add(
   faUsers,
@@ -82,13 +84,16 @@ const NavPanel = {
     }
   },
   computed: {
+    ...mapPiniaState(useAnnouncementsStore, {
+      unreadAnnouncementCount: 'unreadAnnouncementCount',
+      supportsAnnouncements: store => store.supportsAnnouncements
+    }),
     ...mapState({
       currentUser: state => state.users.currentUser,
       followRequestCount: state => state.api.followRequests.length,
       privateMode: state => state.instance.private,
       federating: state => state.instance.federating,
       pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
-      supportsAnnouncements: state => state.announcements.supportsAnnouncements,
       pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
       collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav
     }),
@@ -120,7 +125,7 @@ const NavPanel = {
         }
       )
     },
-    ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+    ...mapGetters(['unreadChatCount'])
   }
 }
 
diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js
@@ -76,6 +76,7 @@ export const ROOT_ITEMS = {
     route: 'announcements',
     icon: 'bullhorn',
     label: 'nav.announcements',
+    store: 'announcements',
     badgeGetter: 'unreadAnnouncementCount',
     criteria: ['announcements']
   }
diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js
@@ -3,6 +3,8 @@ import { routeTo } from 'src/components/navigation/navigation.js'
 import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
+import { mapStores } from 'pinia'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 library.add(faThumbtack)
 
@@ -31,6 +33,7 @@ const NavigationEntry = {
     getters () {
       return this.$store.getters
     },
+    ...mapStores(useAnnouncementsStore),
     ...mapState({
       currentUser: state => state.users.currentUser,
       pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue
@@ -39,6 +39,12 @@
       >
         {{ getters[item.badgeGetter] }}
       </div>
+      <div
+        v-else-if="item.badgeGetter && item.store && this[`${item.store}Store`][item.badgeGetter]"
+        class="badge badge-notification"
+      >
+        {{ this[`${item.store}Store`][item.badgeGetter] }}
+      </div>
       <button
         v-if="showPin && currentUser"
         type="button"
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
@@ -1,5 +1,6 @@
 import { computed } from 'vue'
 import { mapGetters } from 'vuex'
+import { mapState } from 'pinia'
 import Notification from '../notification/notification.vue'
 import NotificationFilters from './notification_filters.vue'
 import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
@@ -12,6 +13,7 @@ import FaviconService from '../../services/favicon_service/favicon_service.js'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
 import { useInterfaceStore } from '../../stores/interface'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 library.add(
   faCircleNotch,
@@ -95,7 +97,8 @@ const Notifications = {
       return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
     },
     noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
-    ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+    ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
+    ...mapGetters(['unreadChatCount'])
   },
   mounted () {
     this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
@@ -1,4 +1,5 @@
 import { mapState, mapGetters } from 'vuex'
+import { mapState as mapPiniaState } from 'pinia'
 import UserCard from '../user_card/user_card.vue'
 import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
 import GestureService from '../../services/gesture_service/gesture_service'
@@ -21,6 +22,7 @@ import {
 } from '@fortawesome/free-solid-svg-icons'
 import { useShoutStore } from '../../stores/shout'
 import { useInterfaceStore } from '../../stores/interface'
+import { useAnnouncementsStore } from '../../stores/announcements'
 
 library.add(
   faSignInAlt,
@@ -96,11 +98,14 @@ const SideDrawer = {
         return { name }
       }
     },
+    ...mapPiniaState(useAnnouncementsStore, {
+      supportsAnnouncements: store => store.supportsAnnouncements,
+      unreadAnnouncementCount: 'unreadAnnouncementCount'
+    }),
     ...mapState({
-      pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
-      supportsAnnouncements: state => state.announcements.supportsAnnouncements
+      pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
     }),
-    ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+    ...mapGetters(['unreadChatCount'])
   },
   methods: {
     toggleDrawer () {
diff --git a/src/main.js b/src/main.js
@@ -18,7 +18,6 @@ import oauthTokensModule from './modules/oauth_tokens.js'
 import reportsModule from './modules/reports.js'
 
 import chatsModule from './modules/chats.js'
-import announcementsModule from './modules/announcements.js'
 
 import { createI18n } from 'vue-i18n'
 
@@ -77,8 +76,7 @@ const persistedStateOptions = {
       authFlow: authFlowModule,
       oauthTokens: oauthTokensModule,
       reports: reportsModule,
-      chats: chatsModule,
-      announcements: announcementsModule
+      chats: chatsModule
     },
     plugins,
     strict: false // Socket modifies itself, let's ignore this for now.
diff --git a/src/modules/announcements.js b/src/modules/announcements.js
@@ -1,135 +0,0 @@
-const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
-
-export const defaultState = {
-  announcements: [],
-  supportsAnnouncements: true,
-  fetchAnnouncementsTimer: undefined
-}
-
-export const mutations = {
-  setAnnouncements (state, announcements) {
-    state.announcements = announcements
-  },
-  setAnnouncementRead (state, { id, read }) {
-    const index = state.announcements.findIndex(a => a.id === id)
-
-    if (index < 0) {
-      return
-    }
-
-    state.announcements[index].read = read
-  },
-  setFetchAnnouncementsTimer (state, timer) {
-    state.fetchAnnouncementsTimer = timer
-  },
-  setSupportsAnnouncements (state, supportsAnnouncements) {
-    state.supportsAnnouncements = supportsAnnouncements
-  }
-}
-
-export const getters = {
-  unreadAnnouncementCount (state, _getters, rootState) {
-    if (!rootState.users.currentUser) {
-      return 0
-    }
-
-    const unread = state.announcements.filter(announcement => !(announcement.inactive || announcement.read))
-    return unread.length
-  }
-}
-
-const announcements = {
-  state: defaultState,
-  mutations,
-  getters,
-  actions: {
-    fetchAnnouncements (store) {
-      if (!store.state.supportsAnnouncements) {
-        return Promise.resolve()
-      }
-
-      const currentUser = store.rootState.users.currentUser
-      const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
-
-      const getAnnouncements = async () => {
-        if (!isAdmin) {
-          return store.rootState.api.backendInteractor.fetchAnnouncements()
-        }
-
-        const all = await store.rootState.api.backendInteractor.adminFetchAnnouncements()
-        const visible = await store.rootState.api.backendInteractor.fetchAnnouncements()
-        const visibleObject = visible.reduce((a, c) => {
-          a[c.id] = c
-          return a
-        }, {})
-        const getWithinVisible = announcement => visibleObject[announcement.id]
-
-        all.forEach(announcement => {
-          const visibleAnnouncement = getWithinVisible(announcement)
-          if (!visibleAnnouncement) {
-            announcement.inactive = true
-          } else {
-            announcement.read = visibleAnnouncement.read
-          }
-        })
-
-        return all
-      }
-
-      return getAnnouncements()
-        .then(announcements => {
-          store.commit('setAnnouncements', announcements)
-        })
-        .catch(error => {
-          // If and only if backend does not support announcements, it would return 404.
-          // In this case, silently ignores it.
-          if (error && error.statusCode === 404) {
-            store.commit('setSupportsAnnouncements', false)
-          } else {
-            throw error
-          }
-        })
-    },
-    markAnnouncementAsRead (store, id) {
-      return store.rootState.api.backendInteractor.dismissAnnouncement({ id })
-        .then(() => {
-          store.commit('setAnnouncementRead', { id, read: true })
-        })
-    },
-    startFetchingAnnouncements (store) {
-      if (store.state.fetchAnnouncementsTimer) {
-        return
-      }
-
-      const interval = setInterval(() => store.dispatch('fetchAnnouncements'), FETCH_ANNOUNCEMENT_INTERVAL_MS)
-      store.commit('setFetchAnnouncementsTimer', interval)
-
-      return store.dispatch('fetchAnnouncements')
-    },
-    stopFetchingAnnouncements (store) {
-      const interval = store.state.fetchAnnouncementsTimer
-      store.commit('setFetchAnnouncementsTimer', undefined)
-      clearInterval(interval)
-    },
-    postAnnouncement (store, { content, startsAt, endsAt, allDay }) {
-      return store.rootState.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
-        .then(() => {
-          return store.dispatch('fetchAnnouncements')
-        })
-    },
-    editAnnouncement (store, { id, content, startsAt, endsAt, allDay }) {
-      return store.rootState.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
-        .then(() => {
-          return store.dispatch('fetchAnnouncements')
-        })
-    },
-    deleteAnnouncement (store, id) {
-      return store.rootState.api.backendInteractor.deleteAnnouncement({ id })
-        .then(() => {
-          return store.dispatch('fetchAnnouncements')
-        })
-    }
-  }
-}
-
-export default announcements
diff --git a/src/stores/announcements.js b/src/stores/announcements.js
@@ -0,0 +1,115 @@
+import { defineStore } from 'pinia'
+
+const FETCH_ANNOUNCEMENT_INTERVAL_MS = 1000 * 60 * 5
+
+export const useAnnouncementsStore = defineStore('announcements', {
+  state: () => ({
+    announcements: [],
+    supportsAnnouncements: true,
+    fetchAnnouncementsTimer: undefined
+  }),
+  getters: {
+    unreadAnnouncementCount () {
+      if (!window.vuex.state.users.currentUser) {
+        return 0
+      }
+
+      const unread = this.announcements.filter(announcement => !(announcement.inactive || announcement.read))
+      return unread.length
+    }
+  },
+  actions: {
+    fetchAnnouncements () {
+      if (!this.supportsAnnouncements) {
+        return Promise.resolve()
+      }
+
+      const currentUser = window.vuex.state.users.currentUser
+      const isAdmin = currentUser && currentUser.privileges.includes('announcements_manage_announcements')
+
+      const getAnnouncements = async () => {
+        if (!isAdmin) {
+          return window.vuex.state.api.backendInteractor.fetchAnnouncements()
+        }
+
+        const all = await window.vuex.state.api.backendInteractor.adminFetchAnnouncements()
+        const visible = await window.vuex.state.api.backendInteractor.fetchAnnouncements()
+        const visibleObject = visible.reduce((a, c) => {
+          a[c.id] = c
+          return a
+        }, {})
+        const getWithinVisible = announcement => visibleObject[announcement.id]
+
+        all.forEach(announcement => {
+          const visibleAnnouncement = getWithinVisible(announcement)
+          if (!visibleAnnouncement) {
+            announcement.inactive = true
+          } else {
+            announcement.read = visibleAnnouncement.read
+          }
+        })
+
+        return all
+      }
+
+      return getAnnouncements()
+        .then(announcements => {
+          this.announcements = announcements
+        })
+        .catch(error => {
+          // If and only if backend does not support announcements, it would return 404.
+          // In this case, silently ignores it.
+          if (error && error.statusCode === 404) {
+            this.supportsAnnouncements = false
+          } else {
+            throw error
+          }
+        })
+    },
+    markAnnouncementAsRead (id) {
+      return window.vuex.state.api.backendInteractor.dismissAnnouncement({ id })
+        .then(() => {
+          const index = this.announcements.findIndex(a => a.id === id)
+
+          if (index < 0) {
+            return
+          }
+
+          this.announcements[index].read = true
+        })
+    },
+    startFetchingAnnouncements () {
+      if (this.fetchAnnouncementsTimer) {
+        return
+      }
+
+      const interval = setInterval(() => this.fetchAnnouncements(), FETCH_ANNOUNCEMENT_INTERVAL_MS)
+      this.fetchAnnouncementsTimer = interval
+
+      return this.fetchAnnouncements()
+    },
+    stopFetchingAnnouncements () {
+      const interval = this.fetchAnnouncementsTimer
+      this.fetchAnnouncementsTimer = undefined
+      clearInterval(interval)
+    },
+    postAnnouncement ({ content, startsAt, endsAt, allDay }) {
+      return window.vuex.state.api.backendInteractor.postAnnouncement({ content, startsAt, endsAt, allDay })
+        .then(() => {
+          return this.fetchAnnouncements()
+        })
+    },
+    editAnnouncement ({ id, content, startsAt, endsAt, allDay }) {
+      return window.vuex.state.api.backendInteractor.editAnnouncement({ id, content, startsAt, endsAt, allDay })
+        .then(() => {
+          return this.fetchAnnouncements()
+        })
+    },
+    deleteAnnouncement (id) {
+      return window.vuex.state.api.backendInteractor.deleteAnnouncement({ id })
+        .then(() => {
+          return this.fetchAnnouncements()
+        })
+    }
+  }
+})