OnlineNotification.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { useWebNotification, whenever } from '@vueuse/core'
  4. import { onMounted, watch, ref } from 'vue'
  5. import CommonPopover from '#shared/components/CommonPopover/CommonPopover.vue'
  6. import { usePopover } from '#shared/components/CommonPopover/usePopover.ts'
  7. import { useActivityMessage } from '#shared/composables/activity-message/useActivityMessage.ts'
  8. import { useBrowserNotifications } from '#shared/composables/useBrowserNotifications.ts'
  9. import { useOnlineNotificationSound } from '#shared/composables/useOnlineNotification/useOnlineNotificationSound.ts'
  10. import { useOnlineNotificationCount } from '#shared/entities/online-notification/composables/useOnlineNotificationCount.ts'
  11. import { useOnlineNotificationList } from '#shared/entities/online-notification/composables/useOnlineNotificationList.ts'
  12. import { cleanupMarkup } from '#shared/utils/markup.ts'
  13. import NotificationButton from '#desktop/components/layout/LayoutSidebar/LeftSidebar/LeftSidebarHeader/OnlineNotification/NotificationButton.vue'
  14. import NotificationPopover from '#desktop/components/layout/LayoutSidebar/LeftSidebar/LeftSidebarHeader/OnlineNotification/NotificationPopover.vue'
  15. const { unseenCount } = useOnlineNotificationCount()
  16. const { popover, popoverTarget, toggle, close } = usePopover()
  17. const { play, isEnabled } = useOnlineNotificationSound()
  18. const { notificationPermission, isGranted, requestNotification } =
  19. useBrowserNotifications()
  20. const { show, isSupported } = useWebNotification()
  21. const {
  22. notificationList,
  23. loading: isLoading,
  24. hasUnseenNotification,
  25. refetch,
  26. } = useOnlineNotificationList()
  27. const watcher = whenever(unseenCount, (newCount, oldCount) => {
  28. if (!isSupported.value) return watcher.stop()
  29. if (!isGranted.value && newCount > oldCount) return
  30. const notification = notificationList.value.at(-1)
  31. if (!notification) return
  32. const { message } = useActivityMessage(ref(notification))
  33. const title = cleanupMarkup(message)
  34. show({
  35. title,
  36. icon: `/assets/images/logo.svg`,
  37. tag: notification.id,
  38. silent: true,
  39. })
  40. })
  41. watch(
  42. unseenCount,
  43. (newCount, oldCount) => {
  44. if (!isEnabled.value || !oldCount) return
  45. if (newCount > oldCount && isGranted.value) play()
  46. },
  47. {
  48. flush: 'post',
  49. },
  50. )
  51. /**
  52. * ⚠️ Browsers enforce user interaction before allowing media playback
  53. * @chrome https://developer.chrome.com/blog/autoplay
  54. * @firefox https://support.mozilla.org/en-US/kb/block-autoplay
  55. */
  56. onMounted(() => {
  57. // If notificationPermission is undefined, we never have asked for permission
  58. if (isEnabled.value && !notificationPermission.value) requestNotification()
  59. })
  60. </script>
  61. <template>
  62. <div
  63. id="app-online-notification"
  64. ref="popoverTarget"
  65. :aria-label="$t('Notifications')"
  66. class="relative"
  67. >
  68. <NotificationButton :unseen-count="unseenCount" @show="toggle(true)" />
  69. <CommonPopover ref="popover" orientation="right" :owner="popoverTarget">
  70. <NotificationPopover
  71. :unseen-count="unseenCount"
  72. :loading="isLoading"
  73. :has-unseen-notification="hasUnseenNotification"
  74. :notification-list="notificationList"
  75. @refetch="refetch"
  76. @close="close"
  77. />
  78. </CommonPopover>
  79. </div>
  80. </template>