logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/

notifications.js (7939B)


  1. import { computed } from 'vue'
  2. import { mapGetters } from 'vuex'
  3. import { mapState } from 'pinia'
  4. import Notification from '../notification/notification.vue'
  5. import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
  6. import NotificationFilters from './notification_filters.vue'
  7. import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
  8. import {
  9. notificationsFromStore,
  10. filteredNotificationsFromStore,
  11. unseenNotificationsFromStore,
  12. countExtraNotifications,
  13. ACTIONABLE_NOTIFICATION_TYPES
  14. } from '../../services/notification_utils/notification_utils.js'
  15. import FaviconService from '../../services/favicon_service/favicon_service.js'
  16. import { library } from '@fortawesome/fontawesome-svg-core'
  17. import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
  18. import { useInterfaceStore } from 'src/stores/interface'
  19. import { useAnnouncementsStore } from 'src/stores/announcements'
  20. library.add(
  21. faCircleNotch,
  22. faArrowUp,
  23. faMinus
  24. )
  25. const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
  26. const Notifications = {
  27. components: {
  28. Notification,
  29. NotificationFilters,
  30. ExtraNotifications
  31. },
  32. props: {
  33. // Disables panel styles, unread mark, potentially other notification-related actions
  34. // meant for "Interactions" timeline
  35. minimalMode: Boolean,
  36. // Custom filter mode, an array of strings, possible values 'mention', 'status', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
  37. filterMode: Array,
  38. // Do not show extra notifications
  39. noExtra: {
  40. type: Boolean,
  41. default: false
  42. },
  43. // Disable teleporting (i.e. for /users/user/notifications)
  44. disableTeleport: Boolean
  45. },
  46. data () {
  47. return {
  48. showScrollTop: false,
  49. bottomedOut: false,
  50. // How many seen notifications to display in the list. The more there are,
  51. // the heavier the page becomes. This count is increased when loading
  52. // older notifications, and cut back to default whenever hitting "Read!".
  53. seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
  54. }
  55. },
  56. provide () {
  57. return {
  58. popoversZLayer: computed(() => this.popoversZLayer)
  59. }
  60. },
  61. computed: {
  62. mainClass () {
  63. return this.minimalMode ? '' : 'panel panel-default'
  64. },
  65. notifications () {
  66. return notificationsFromStore(this.$store)
  67. },
  68. error () {
  69. return this.$store.state.notifications.error
  70. },
  71. unseenNotifications () {
  72. return unseenNotificationsFromStore(this.$store)
  73. },
  74. filteredNotifications () {
  75. if (this.unseenAtTop) {
  76. return [
  77. ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
  78. ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
  79. ]
  80. } else {
  81. return filteredNotificationsFromStore(this.$store, this.filterMode)
  82. }
  83. },
  84. unseenCountBadgeText () {
  85. return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
  86. },
  87. unseenCount () {
  88. return this.unseenNotifications.length
  89. },
  90. ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
  91. extraNotificationsCount () {
  92. return countExtraNotifications(this.$store)
  93. },
  94. unseenCountTitle () {
  95. return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
  96. },
  97. loading () {
  98. return this.$store.state.notifications.loading
  99. },
  100. noHeading () {
  101. const { layoutType } = useInterfaceStore()
  102. return this.minimalMode || layoutType === 'mobile'
  103. },
  104. teleportTarget () {
  105. const { layoutType } = useInterfaceStore()
  106. const map = {
  107. wide: '#notifs-column',
  108. mobile: '#mobile-notifications'
  109. }
  110. return map[layoutType] || '#notifs-sidebar'
  111. },
  112. popoversZLayer () {
  113. const { layoutType } = useInterfaceStore()
  114. return layoutType === 'mobile' ? 'navbar' : null
  115. },
  116. notificationsToDisplay () {
  117. return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
  118. },
  119. noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
  120. unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
  121. showExtraNotifications () {
  122. return !this.noExtra
  123. },
  124. ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']),
  125. ...mapGetters(['unreadChatCount'])
  126. },
  127. mounted () {
  128. this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
  129. if (!this.scrollerRef) {
  130. this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
  131. }
  132. if (!this.scrollerRef) {
  133. this.scrollerRef = this.$refs.root.closest('.column.main')
  134. }
  135. this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
  136. },
  137. unmounted () {
  138. if (!this.scrollerRef) return
  139. this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
  140. },
  141. watch: {
  142. unseenCountTitle (count) {
  143. if (count > 0) {
  144. FaviconService.drawFaviconBadge()
  145. useInterfaceStore().setPageTitle(`(${count})`)
  146. } else {
  147. FaviconService.clearFaviconBadge()
  148. useInterfaceStore().setPageTitle('')
  149. }
  150. },
  151. teleportTarget () {
  152. // handle scroller change
  153. this.$nextTick(() => {
  154. this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
  155. this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
  156. if (!this.scrollerRef) {
  157. this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
  158. }
  159. this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
  160. this.updateScrollPosition()
  161. })
  162. }
  163. },
  164. methods: {
  165. scrollToTop () {
  166. const scrollable = this.scrollerRef
  167. scrollable.scrollTo({ top: this.$refs.root.offsetTop })
  168. },
  169. updateScrollPosition () {
  170. this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
  171. },
  172. shouldShowUnseen (notification) {
  173. if (notification.seen) return false
  174. const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
  175. return this.ignoreInactionableSeen ? actionable : true
  176. },
  177. /* "Interacted" really refers to "actionable" notifications that require user input,
  178. * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear
  179. * the "seen" status upon any clicks on them
  180. */
  181. notificationClicked (notification) {
  182. const { id } = notification
  183. this.$store.dispatch('notificationClicked', { id })
  184. },
  185. notificationInteracted (notification) {
  186. const { id } = notification
  187. this.$store.dispatch('markSingleNotificationAsSeen', { id })
  188. },
  189. markAsSeen () {
  190. this.$store.dispatch('markNotificationsAsSeen')
  191. this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
  192. },
  193. fetchOlderNotifications () {
  194. if (this.loading) {
  195. return
  196. }
  197. const seenCount = this.filteredNotifications.length - this.unseenCount
  198. if (this.seenToDisplayCount < seenCount) {
  199. this.seenToDisplayCount = Math.min(this.seenToDisplayCount + 20, seenCount)
  200. return
  201. } else if (this.seenToDisplayCount > seenCount) {
  202. this.seenToDisplayCount = seenCount
  203. }
  204. const store = this.$store
  205. const credentials = store.state.users.currentUser.credentials
  206. store.commit('setNotificationsLoading', { value: true })
  207. notificationsFetcher.fetchAndUpdate({
  208. store,
  209. credentials,
  210. older: true
  211. }).then(notifs => {
  212. store.commit('setNotificationsLoading', { value: false })
  213. if (notifs.length === 0) {
  214. this.bottomedOut = true
  215. }
  216. this.seenToDisplayCount += notifs.length
  217. })
  218. }
  219. }
  220. }
  221. export default Notifications