aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHJ <30-hj@users.noreply.git.pleroma.social>2023-12-13 22:20:59 +0000
committerHJ <30-hj@users.noreply.git.pleroma.social>2023-12-13 22:20:59 +0000
commit2b41c1cfe86cba814aba98da14d70ac1c09f422f (patch)
treeb90119b33d0d9cbcccc9193809864fe630020bf6
parenta98e241a815a95a2a7dd073aec8a993719037dcc (diff)
parent55ed65331fb5e53374a0be6710896c383e34d120 (diff)
Merge branch 'notifications-thru-sw' into 'develop'
Notifications improvements. See merge request pleroma/pleroma-fe!1873
-rw-r--r--changelog.d/double-notifications.fix1
-rw-r--r--changelog.d/focus-clear.add1
-rw-r--r--changelog.d/mobile-chrome-notifs.fix1
-rw-r--r--changelog.d/mobile-drawer-notifications.change1
-rw-r--r--changelog.d/more-notification-types-setting.fix1
-rw-r--r--changelog.d/native-filtering.add1
-rw-r--r--changelog.d/native-notifications.add1
-rw-r--r--changelog.d/noninteractive-ignore-read.add1
-rw-r--r--changelog.d/notification-read.add1
-rw-r--r--changelog.d/notifications-sorting.change1
-rw-r--r--changelog.d/serviceworkers.change1
-rw-r--r--changelog.d/unreads-sync.fix1
-rw-r--r--changelog.d/web-push-always.add1
-rw-r--r--src/boot/after_store.js4
-rw-r--r--src/components/mobile_nav/mobile_nav.js18
-rw-r--r--src/components/mobile_nav/mobile_nav.vue19
-rw-r--r--src/components/notification/notification.js6
-rw-r--r--src/components/notification/notification.vue3
-rw-r--r--src/components/notifications/notifications.js37
-rw-r--r--src/components/notifications/notifications.vue8
-rw-r--r--src/components/quick_view_settings/quick_view_settings.js1
-rw-r--r--src/components/report/report.js1
-rw-r--r--src/components/settings_modal/settings_modal.scss12
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.js4
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue189
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js1
-rw-r--r--src/components/status/status.js3
-rw-r--r--src/components/status/status.vue3
-rw-r--r--src/i18n/en.json13
-rw-r--r--src/lib/persisted_state.js8
-rw-r--r--src/main.js2
-rw-r--r--src/modules/adminSettings.js2
-rw-r--r--src/modules/config.js18
-rw-r--r--src/modules/instance.js3
-rw-r--r--src/modules/notifications.js169
-rw-r--r--src/modules/serverSideStorage.js1
-rw-r--r--src/modules/statuses.js161
-rw-r--r--src/modules/users.js6
-rw-r--r--src/services/api/api.service.js4
-rw-r--r--src/services/desktop_notification_utils/desktop_notification_utils.js39
-rw-r--r--src/services/entity_normalizer/entity_normalizer.service.js1
-rw-r--r--src/services/favicon_service/favicon_service.js5
-rw-r--r--src/services/notification_utils/notification_utils.js81
-rw-r--r--src/services/notifications_fetcher/notifications_fetcher.service.js14
-rw-r--r--src/services/sw/sw.js (renamed from src/services/push/push.js)55
-rw-r--r--src/sw.js98
-rw-r--r--test/unit/specs/modules/statuses.spec.js76
-rw-r--r--test/unit/specs/services/notification_utils/notification_utils.spec.js78
48 files changed, 771 insertions, 385 deletions
diff --git a/changelog.d/double-notifications.fix b/changelog.d/double-notifications.fix
new file mode 100644
index 00000000..24e08c0f
--- /dev/null
+++ b/changelog.d/double-notifications.fix
@@ -0,0 +1 @@
+Fix native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
diff --git a/changelog.d/focus-clear.add b/changelog.d/focus-clear.add
new file mode 100644
index 00000000..70f54ab6
--- /dev/null
+++ b/changelog.d/focus-clear.add
@@ -0,0 +1 @@
+Focusing into a tab clears all current desktop notifications
diff --git a/changelog.d/mobile-chrome-notifs.fix b/changelog.d/mobile-chrome-notifs.fix
new file mode 100644
index 00000000..7db10c56
--- /dev/null
+++ b/changelog.d/mobile-chrome-notifs.fix
@@ -0,0 +1 @@
+Fixed error that appeared on mobile Chrome(ium) (and derivatives) when native notifications are allowed
diff --git a/changelog.d/mobile-drawer-notifications.change b/changelog.d/mobile-drawer-notifications.change
new file mode 100644
index 00000000..9353c709
--- /dev/null
+++ b/changelog.d/mobile-drawer-notifications.change
@@ -0,0 +1 @@
+Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
diff --git a/changelog.d/more-notification-types-setting.fix b/changelog.d/more-notification-types-setting.fix
new file mode 100644
index 00000000..2d71b599
--- /dev/null
+++ b/changelog.d/more-notification-types-setting.fix
@@ -0,0 +1 @@
+Fixed being unable to set notification visibility for reports and follow requests
diff --git a/changelog.d/native-filtering.add b/changelog.d/native-filtering.add
new file mode 100644
index 00000000..82ab9a23
--- /dev/null
+++ b/changelog.d/native-filtering.add
@@ -0,0 +1 @@
+Added option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
diff --git a/changelog.d/native-notifications.add b/changelog.d/native-notifications.add
new file mode 100644
index 00000000..d896e7c0
--- /dev/null
+++ b/changelog.d/native-notifications.add
@@ -0,0 +1 @@
+Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
diff --git a/changelog.d/noninteractive-ignore-read.add b/changelog.d/noninteractive-ignore-read.add
new file mode 100644
index 00000000..5e8710cf
--- /dev/null
+++ b/changelog.d/noninteractive-ignore-read.add
@@ -0,0 +1 @@
+Added option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
diff --git a/changelog.d/notification-read.add b/changelog.d/notification-read.add
new file mode 100644
index 00000000..e5027a95
--- /dev/null
+++ b/changelog.d/notification-read.add
@@ -0,0 +1 @@
+Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
diff --git a/changelog.d/notifications-sorting.change b/changelog.d/notifications-sorting.change
new file mode 100644
index 00000000..3a616244
--- /dev/null
+++ b/changelog.d/notifications-sorting.change
@@ -0,0 +1 @@
+Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
diff --git a/changelog.d/serviceworkers.change b/changelog.d/serviceworkers.change
new file mode 100644
index 00000000..b3b64f6d
--- /dev/null
+++ b/changelog.d/serviceworkers.change
@@ -0,0 +1 @@
+Notifications are now shown through a serviceworker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
diff --git a/changelog.d/unreads-sync.fix b/changelog.d/unreads-sync.fix
new file mode 100644
index 00000000..1eac3364
--- /dev/null
+++ b/changelog.d/unreads-sync.fix
@@ -0,0 +1 @@
+unread notifications should now properly catch up (eventually) in polling mode
diff --git a/changelog.d/web-push-always.add b/changelog.d/web-push-always.add
new file mode 100644
index 00000000..f8b8888a
--- /dev/null
+++ b/changelog.d/web-push-always.add
@@ -0,0 +1 @@
+Added option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 395d4834..84fea954 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -16,6 +16,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js'
import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js'
+import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
let staticInitialResults = null
@@ -344,6 +345,9 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService()
+ initServiceWorker(store)
+
+ window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index b5325116..8c9261b0 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -14,7 +14,8 @@ import {
faBell,
faBars,
faArrowUp,
- faMinus
+ faMinus,
+ faCheckDouble
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -22,7 +23,8 @@ library.add(
faBell,
faBars,
faArrowUp,
- faMinus
+ faMinus,
+ faCheckDouble
)
const MobileNav = {
@@ -55,6 +57,12 @@ const MobileNav = {
unseenNotificationsCount () {
return this.unseenNotifications.length + countExtraNotifications(this.$store)
},
+ unseenCount () {
+ return this.unseenNotifications.length
+ },
+ unseenCountBadgeText () {
+ return `${this.unseenCount ? this.unseenCount : ''}`
+ },
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name },
isChat () {
@@ -67,6 +75,9 @@ const MobileNav = {
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
},
+ closingDrawerMarksAsSeen () {
+ return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen
+ },
...mapGetters(['unreadChatCount'])
},
methods: {
@@ -81,7 +92,7 @@ const MobileNav = {
// make sure to mark notifs seen only when the notifs were open and not
// from close-calls.
this.notificationsOpen = false
- if (markRead) {
+ if (markRead && this.closingDrawerMarksAsSeen) {
this.markNotificationsAsSeen()
}
}
@@ -117,7 +128,6 @@ const MobileNav = {
this.hideConfirmLogout()
},
markNotificationsAsSeen () {
- // this.$refs.notifications.markAsSeen()
this.$store.dispatch('markNotificationsAsSeen')
},
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index c2746abe..f20a509d 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -50,7 +50,13 @@
@touchmove.stop="notificationsTouchMove"
>
<div class="mobile-notifications-header">
- <span class="title">{{ $t('notifications.notifications') }}</span>
+ <span class="title">
+ {{ $t('notifications.notifications') }}
+ <span
+ v-if="unseenCountBadgeText"
+ class="badge badge-notification unseen-count"
+ >{{ unseenCountBadgeText }}</span>
+ </span>
<span class="spacer" />
<button
v-if="notificationsAtTop"
@@ -67,6 +73,17 @@
</FALayers>
</button>
<button
+ v-if="!closingDrawerMarksAsSeen"
+ class="button-unstyled mobile-nav-button"
+ :title="$t('nav.mobile_notifications_mark_as_seen')"
+ @click.stop.prevent="markNotificationsAsSeen()"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="check-double"
+ />
+ </button>
+ <button
class="button-unstyled mobile-nav-button"
:title="$t('nav.mobile_notifications_close')"
@click.stop.prevent="closeMobileNotifications(true)"
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 420db4f0..0e938c42 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -50,6 +50,7 @@ const Notification = {
}
},
props: ['notification'],
+ emits: ['interacted'],
components: {
StatusContent,
UserAvatar,
@@ -72,6 +73,9 @@ const Notification = {
getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
},
+ interacted () {
+ this.$emit('interacted')
+ },
toggleMute () {
this.unmuted = !this.unmuted
},
@@ -95,6 +99,7 @@ const Notification = {
}
},
doApprove () {
+ this.$emit('interacted')
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
@@ -114,6 +119,7 @@ const Notification = {
}
},
doDeny () {
+ this.$emit('interacted')
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 6b3315f9..a8eceab0 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>
@@ -248,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 571df0f1..a9fa8455 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -8,7 +8,8 @@ import {
notificationsFromStore,
filteredNotificationsFromStore,
unseenNotificationsFromStore,
- countExtraNotifications
+ countExtraNotifications,
+ ACTIONABLE_NOTIFICATION_TYPES
} from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -65,13 +66,20 @@ 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)
},
filteredNotifications () {
- return filteredNotificationsFromStore(this.$store, this.filterMode)
+ if (this.unseenAtTop) {
+ return [
+ ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
+ ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
+ ]
+ } else {
+ return filteredNotificationsFromStore(this.$store, this.filterMode)
+ }
},
unseenCountBadgeText () {
return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
@@ -79,6 +87,7 @@ const Notifications = {
unseenCount () {
return this.unseenNotifications.length
},
+ ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
extraNotificationsCount () {
return countExtraNotifications(this.$store)
},
@@ -86,7 +95,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
@@ -108,6 +117,7 @@ const Notifications = {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
showExtraNotifications () {
return !this.noExtra
},
@@ -154,11 +164,28 @@ const Notifications = {
scrollToTop () {
const scrollable = this.scrollerRef
scrollable.scrollTo({ top: this.$refs.root.offsetTop })
- // this.$refs.root.scrollIntoView({ behavior: 'smooth', block: 'start' })
},
updateScrollPosition () {
this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
},
+ shouldShowUnseen (notification) {
+ if (notification.seen) return false
+
+ const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
+ return this.ignoreInactionableSeen ? actionable : true
+ },
+ /* "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 { id } = notification
+ this.$store.dispatch('notificationClicked', { id })
+ },
+ notificationInteracted (notification) {
+ const { id } = notification
+ this.$store.dispatch('markSingleNotificationAsSeen', { id })
+ },
markAsSeen () {
this.$store.dispatch('markNotificationsAsSeen')
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 999f8e9c..a0025182 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -66,10 +66,14 @@
:key="notification.id"
role="listitem"
class="notification"
- :class="{unseen: !minimalMode && !notification.seen}"
+ :class="{unseen: !minimalMode && shouldShowUnseen(notification)}"
+ @click="e => notificationClicked(notification)"
>
<div class="notification-overlay" />
- <notification :notification="notification" />
+ <notification
+ :notification="notification"
+ @interacted="e => notificationInteracted(notification)"
+ />
</div>
</div>
<div class="panel-footer">
diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js
index 2798f37a..67aa6713 100644
--- a/src/components/quick_view_settings/quick_view_settings.js
+++ b/src/components/quick_view_settings/quick_view_settings.js
@@ -52,7 +52,6 @@ const QuickViewSettings = {
get () { return this.mergedConfig.mentionLinkShowAvatar },
set () {
const value = !this.showUserAvatars
- console.log(value)
this.$store.dispatch('setOption', { name: 'mentionLinkShowAvatar', value })
}
},
diff --git a/src/components/report/report.js b/src/components/report/report.js
index 5685aa25..f8675c0f 100644
--- a/src/components/report/report.js
+++ b/src/components/report/report.js
@@ -16,7 +16,6 @@ const Report = {
},
computed: {
report () {
- console.log(this.$store.state.reports.reports[this.reportId] || {})
return this.$store.state.reports.reports[this.reportId] || {}
},
state: {
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 49ef83e0..6bc9459b 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -3,6 +3,10 @@
.settings-modal {
overflow: hidden;
+ h4 {
+ margin-bottom: 0.5em;
+ }
+
.setting-list,
.option-list {
list-style-type: none;
@@ -15,6 +19,14 @@
.suboptions {
margin-top: 0.3em;
}
+
+ &.two-column {
+ column-count: 2;
+
+ > li {
+ break-inside: avoid;
+ }
+ }
}
.setting-description {
diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js
index 3c6ab87f..c53b5889 100644
--- a/src/components/settings_modal/tabs/notifications_tab.js
+++ b/src/components/settings_modal/tabs/notifications_tab.js
@@ -16,6 +16,10 @@ const NotificationsTab = {
user () {
return this.$store.state.users.currentUser
},
+ canReceiveReports () {
+ if (!this.user) { return false }
+ return this.user.privileges.includes('reports_manage_reports')
+ },
...SharedComputedObject()
},
methods: {
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index 4dfba444..9ace4c36 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -1,6 +1,31 @@
<template>
<div :label="$t('settings.notifications')">
<div class="setting-item">
+ <h2>{{ $t('settings.notification_setting_annoyance') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="closingDrawerMarksAsSeen">
+ {{ $t('settings.notification_setting_drawer_marks_as_seen') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="ignoreInactionableSeen">
+ {{ $t('settings.notification_setting_ignore_inactionable_seen') }}
+ </BooleanSetting>
+ <div>
+ <small>
+ {{ $t('settings.notification_setting_ignore_inactionable_seen_tip') }}
+ </small>
+ </div>
+ </li>
+ <li>
+ <BooleanSetting path="unseenAtTop" expert="1">
+ {{ $t('settings.notification_setting_unseen_at_top') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
<ul class="setting-list">
<li>
@@ -11,43 +36,144 @@
{{ $t('settings.notification_setting_block_from_strangers') }}
</BooleanSetting>
</li>
- <li class="select-multiple">
- <span class="label">{{ $t('settings.notification_visibility') }}</span>
- <ul class="option-list">
+ <li>
+ <h3> {{ $t('settings.notification_visibility') }}</h3>
+ <p v-if="expertLevel > 0">{{ $t('settings.notification_setting_filters_chrome_push') }}</p>
+ <ul class="setting-list two-column">
<li>
- <BooleanSetting path="notificationVisibility.likes">
- {{ $t('settings.notification_visibility_likes') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_mentions') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.mentions">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.mentions">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.repeats">
- {{ $t('settings.notification_visibility_repeats') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_likes') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.likes">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.likes">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.follows">
- {{ $t('settings.notification_visibility_follows') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_repeats') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.repeats">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.repeats">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.mentions">
- {{ $t('settings.notification_visibility_mentions') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_emoji_reactions') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.emojiReactions">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.emojiReactions">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.moves">
- {{ $t('settings.notification_visibility_moves') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_follows') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.follows">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.follows">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.emojiReactions">
- {{ $t('settings.notification_visibility_emoji_reactions') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_follow_requests') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.followRequest">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.followRequest">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.polls">
- {{ $t('settings.notification_visibility_polls') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_moves') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.moves">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.moves">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_polls') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.polls">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.polls">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li v-if="canReceiveReports">
+ <h4> {{ $t('settings.notification_visibility_reports') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.reports">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.reports">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
</ul>
</li>
@@ -108,6 +234,21 @@
>
{{ $t('settings.enable_web_push_notifications') }}
</BooleanSetting>
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path="webPushAlwaysShowNotifications"
+ :disabled="!mergedConfig.webPushNotifications"
+ >
+ {{ $t('settings.enable_web_push_always_show') }}
+ </BooleanSetting>
+ <div :class="{ faint: !mergedConfig.webPushNotifications }">
+ <small>
+ {{ $t('settings.enable_web_push_always_show_tip') }}
+ </small>
+ </div>
+ </li>
+ </ul>
</li>
<li>
<BooleanSetting
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
index 4a739f73..58f8d44a 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -755,7 +755,6 @@ export default {
selected () {
this.selectedTheme = Object.entries(this.availableStyles).find(([k, s]) => {
if (Array.isArray(s)) {
- console.log(s[0] === this.selected, this.selected)
return s[0] === this.selected
} else {
return s.name === this.selected
diff --git a/src/components/status/status.js b/src/components/status/status.js
index a339694d..129929a3 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -154,6 +154,7 @@ const Status = {
'controlledSetMediaPlaying',
'dive'
],
+ emits: ['interacted'],
data () {
return {
uncontrolledReplying: false,
@@ -442,9 +443,11 @@ const Status = {
this.error = error
},
clearError () {
+ this.$emit('interacted')
this.error = undefined
},
toggleReplying () {
+ this.$emit('interacted')
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 26fafc91..fa9d6f0b 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -531,14 +531,17 @@
:visibility="status.visibility"
:logged-in="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<favorite-button
:logged-in="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<ReactButton
v-if="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<extra-buttons
:status="status"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 6bb9e4bb..f4c9de18 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -189,6 +189,7 @@
"mobile_notifications": "Open notifications",
"mobile_notifications": "Open notifications (there are unread ones)",
"mobile_notifications_close": "Close notifications",
+ "mobile_notifications_mark_as_seen": "Mark all as seen",
"announcements": "Announcements"
},
"notifications": {
@@ -561,10 +562,14 @@
"posts": "Posts",
"user_profiles": "User Profiles",
"notification_visibility": "Types of notifications to show",
+ "notification_visibility_in_column": "Show in notifications column/drawer",
+ "notification_visibility_native_notifications": "Show a native notification",
"notification_visibility_follows": "Follows",
+ "notification_visibility_follow_requests": "Follow requests",
"notification_visibility_likes": "Favorites",
"notification_visibility_mentions": "Mentions",
"notification_visibility_repeats": "Repeats",
+ "notification_visibility_reports": "Reports",
"notification_visibility_moves": "User Migrates",
"notification_visibility_emoji_reactions": "Reactions",
"notification_visibility_polls": "Ends of polls you voted in",
@@ -688,13 +693,21 @@
"greentext": "Meme arrows",
"show_yous": "Show (You)s",
"notifications": "Notifications",
+ "notification_setting_annoyance": "Annoyance",
+ "notification_setting_drawer_marks_as_seen": "Closing drawer (mobile) marks all notifications as read",
+ "notification_setting_ignore_inactionable_seen": "Ignore read state of inactionable notifications (likes, repeats etc)",
+ "notification_setting_ignore_inactionable_seen_tip": "This will not actually mark those notifications as read, and you'll still get desktop notifications about them if you chose so",
+ "notification_setting_unseen_at_top": "Show unread notifications above others",
"notification_setting_filters": "Filters",
+ "notification_setting_filters_chrome_push": "On some browsers (chrome) it might be impossible to completely filter out notifications by type when they arrive by Push",
"notification_setting_block_from_strangers": "Block notifications from users who you do not follow",
"notification_setting_privacy": "Privacy",
"notification_setting_hide_notification_contents": "Hide the sender and contents of push notifications",
"notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
"notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
"enable_web_push_notifications": "Enable web push notifications",
+ "enable_web_push_always_show": "Always show web push notifications",
+ "enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.",
"more_settings": "More settings",
"style": {
"switcher": {
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
index 6d59c595..22ba1e7f 100644
--- a/src/lib/persisted_state.js
+++ b/src/lib/persisted_state.js
@@ -38,7 +38,7 @@ export default function createPersistedState ({
},
setState = (key, state, storage) => {
if (!loaded) {
- console.log('waiting for old state to be loaded...')
+ console.info('waiting for old state to be loaded...')
return Promise.resolve()
} else {
return storage.setItem(key, state)
@@ -65,7 +65,7 @@ export default function createPersistedState ({
}
loaded = true
} catch (e) {
- console.log("Couldn't load state")
+ console.error("Couldn't load state")
console.error(e)
loaded = true
}
@@ -86,8 +86,8 @@ export default function createPersistedState ({
})
}
} catch (e) {
- console.log("Couldn't persist state:")
- console.log(e)
+ console.error("Couldn't persist state:")
+ console.error(e)
}
})
}
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/adminSettings.js b/src/modules/adminSettings.js
index 3df3647f..ef28f6e0 100644
--- a/src/modules/adminSettings.js
+++ b/src/modules/adminSettings.js
@@ -105,7 +105,6 @@ const adminSettingsStorage = {
}
set(config, path, convert(c.value))
})
- console.log(config[':pleroma'])
commit('updateAdminSettings', { config, modifiedPaths })
commit('resetAdminDraft')
},
@@ -123,7 +122,6 @@ const adminSettingsStorage = {
const descriptions = {}
backendDescriptions.forEach(d => convert(d, '', descriptions))
- console.log(descriptions[':pleroma']['Pleroma.Captcha'])
commit('updateAdminDescriptions', { descriptions })
},
diff --git a/src/modules/config.js b/src/modules/config.js
index 49e9b2df..abb57272 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -66,7 +66,20 @@ export const defaultState = {
chatMention: true,
polls: true
},
+ notificationNative: {
+ follows: true,
+ mentions: true,
+ likes: false,
+ repeats: false,
+ moves: false,
+ emojiReactions: false,
+ followRequest: true,
+ reports: true,
+ chatMention: true,
+ polls: true
+ },
webPushNotifications: false,
+ webPushAlwaysShowNotifications: false,
muteWords: [],
highlight: {},
interfaceLanguage: browserLocale,
@@ -124,7 +137,10 @@ export const defaultState = {
showAnnouncementsInExtraNotifications: undefined, // instance default
showFollowRequestsInExtraNotifications: undefined, // instance default
maxDepthInThread: undefined, // instance default
- autocompleteSelect: undefined // instance default
+ autocompleteSelect: undefined, // instance default
+ closingDrawerMarksAsSeen: undefined, // instance default
+ unseenAtTop: undefined, // instance default
+ ignoreInactionableSeen: undefined // instance default
}
// caching the instance default properties
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 034348ff..3022a41a 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -110,6 +110,9 @@ const defaultState = {
showFollowRequestsInExtraNotifications: true,
maxDepthInThread: 6,
autocompleteSelect: false,
+ closingDrawerMarksAsSeen: true,
+ unseenAtTop: false,
+ ignoreInactionableSeen: false,
// Nasty stuff
customEmoji: [],
diff --git a/src/modules/notifications.js b/src/modules/notifications.js
new file mode 100644
index 00000000..c1f5a63e
--- /dev/null
+++ b/src/modules/notifications.js
@@ -0,0 +1,169 @@
+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 = state.idStore[id]
+ if (notification) notification.seen = true
+ },
+ dismissNotification (state, { id }) {
+ state.data = state.data.filter(n => n.id !== id)
+ delete state.idStore[id]
+ },
+ updateNotification (state, { id, updater }) {
+ const notification = state.idStore[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]
+
+ 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)
+ commit('addNewNotifications', { notifications: [notification] })
+
+ maybeShowNotification(store, notification)
+ } else if (notification.seen) {
+ state.idStore[notification.id].seen = true
+ }
+ })
+ },
+ notificationClicked ({ state, dispatch }, { id }) {
+ const notification = state.idStore[id]
+ const { type, seen } = notification
+
+ if (!seen) {
+ switch (type) {
+ case 'mention':
+ case 'pleroma:report':
+ case 'follow_request':
+ break
+ default:
+ dispatch('markSingleNotificationAsSeen', { id })
+ }
+ }
+ },
+ 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/serverSideStorage.js b/src/modules/serverSideStorage.js
index c933ce8d..c55f54fd 100644
--- a/src/modules/serverSideStorage.js
+++ b/src/modules/serverSideStorage.js
@@ -419,7 +419,6 @@ const serverSideStorage = {
actions: {
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
const needPush = state.dirty || force
- console.log(needPush)
if (!needPush) return
commit('updateCache', { username: rootState.users.currentUser.fqn })
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 9b4cd175..462def22 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -12,11 +12,6 @@ import {
isArray,
omitBy
} from 'lodash'
-import {
- isStatusNotification,
- isValidNotification,
- maybeShowNotification
-} from '../services/notification_utils/notification_utils.js'
import apiService from '../services/api/api.service.js'
const emptyTl = (userId = 0) => ({
@@ -36,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(),
@@ -154,22 +139,6 @@ const addStatusToGlobalStorage = (state, data) => {
return result
}
-// 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?
-
- // 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]) {
- remove(state.conversationsObject[conversationId], { id: status.id })
- }
-}
-
const addNewStatuses = (state, { statuses, showImmediately = false, timeline, user = {}, noIdUpdate = false, userId, pagination = {} }) => {
// Sanity check
if (!isArray(statuses)) {
@@ -303,20 +272,6 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
favoriteStatus(favorite)
}
},
- deletion: (deletion) => {
- const uri = deletion.uri
- const status = find(allStatuses, { uri })
- if (!status) {
- return
- }
-
- removeStatusFromGlobalStorage(state, status)
-
- if (timeline) {
- remove(timelineObject.statuses, { uri })
- remove(timelineObject.visibleStatuses, { uri })
- }
- },
follow: (follow) => {
// NOOP, it is known status but we don't do anything about it for now
},
@@ -338,52 +293,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) {
@@ -396,7 +305,6 @@ const removeStatus = (state, { timeline, userId }) => {
export const mutations = {
addNewStatuses,
- addNewNotifications,
removeStatus,
showNewStatuses (state, { timeline }) {
const oldTimeline = (state.timelines[timeline])
@@ -418,9 +326,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]
@@ -503,31 +408,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
},
@@ -609,23 +489,9 @@ 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 })
},
- 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 })
- },
fetchStatus ({ rootState, dispatch }, id) {
return rootState.api.backendInteractor.fetchStatus({ id })
.then((status) => dispatch('addNewStatuses', { statuses: [status] }))
@@ -721,31 +587,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
- })
- },
- markSingleNotificationAsSeen ({ rootState, commit }, { id }) {
- commit('markSingleNotificationAsSeen', { id })
- apiService.markNotificationsAsSeen({
- single: true,
- id,
- credentials: rootState.users.currentUser.credentials
- })
- },
- 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 50b4cb84..6467d732 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -2,7 +2,7 @@ import backendInteractorService from '../services/backend_interactor_service/bac
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import oauthApi from '../services/new_api/oauth.js'
import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'lodash'
-import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
+import { registerPushNotifications, unregisterPushNotifications } from '../services/sw/sw.js'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -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)
@@ -667,7 +667,7 @@ const users = {
resolve()
})
.catch((error) => {
- console.log(error)
+ console.error(error)
commit('endLogin')
reject(new Error('Failed to connect to server, try again'))
})
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index f45e3958..bde2e163 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -671,6 +671,7 @@ const fetchTimeline = ({
timeline,
credentials,
since = false,
+ minId = false,
until = false,
userId = false,
listId = false,
@@ -705,6 +706,9 @@ const fetchTimeline = ({
url = url(listId)
}
+ if (minId) {
+ params.push(['min_id', minId])
+ }
if (since) {
params.push(['since_id', since])
}
diff --git a/src/services/desktop_notification_utils/desktop_notification_utils.js b/src/services/desktop_notification_utils/desktop_notification_utils.js
index b84a1f75..80b8c6e0 100644
--- a/src/services/desktop_notification_utils/desktop_notification_utils.js
+++ b/src/services/desktop_notification_utils/desktop_notification_utils.js
@@ -1,9 +1,38 @@
+import {
+ showDesktopNotification as swDesktopNotification,
+ closeDesktopNotification as swCloseDesktopNotification,
+ isSWSupported
+} from '../sw/sw.js'
+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)
+ } else if (!state.failCreateNotif) {
+ try {
+ const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts)
+ setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
+ } catch {
+ state.failCreateNotif = true
+ }
+ }
+}
+
+export const closeDesktopNotification = (rootState, { id }) => {
+ if (!('Notification' in window && window.Notification.permission === 'granted')) return
+
+ if (isSWSupported()) {
+ swCloseDesktopNotification({ id })
+ }
+}
+
+export const closeAllDesktopNotifications = (rootState) => {
+ if (!('Notification' in window && window.Notification.permission === 'granted')) return
- const desktopNotification = new window.Notification(desktopNotificationOpts.title, desktopNotificationOpts)
- // Chrome is known for not closing notifications automatically
- // according to MDN, anyway.
- setTimeout(desktopNotification.close.bind(desktopNotification), 5000)
+ if (isSWSupported()) {
+ swCloseDesktopNotification({})
+ }
}
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/favicon_service/favicon_service.js b/src/services/favicon_service/favicon_service.js
index 7e19629d..df603bb4 100644
--- a/src/services/favicon_service/favicon_service.js
+++ b/src/services/favicon_service/favicon_service.js
@@ -55,10 +55,13 @@ const createFaviconService = () => {
})
}
+ const getOriginalFavicons = () => [...favicons]
+
return {
initFaviconService,
clearFaviconBadge,
- drawFaviconBadge
+ drawFaviconBadge,
+ getOriginalFavicons
}
}
diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js
index 815e792d..7b705e65 100644
--- a/src/services/notification_utils/notification_utils.js
+++ b/src/services/notification_utils/notification_utils.js
@@ -1,28 +1,36 @@
-import { filter, sortBy, includes } from 'lodash'
import { muteWordHits } from '../status_parser/status_parser.js'
import { showDesktopNotification } from '../desktop_notification_utils/desktop_notification_utils.js'
-export const notificationsFromStore = store => store.state.statuses.notifications.data
+import FaviconService from 'src/services/favicon_service/favicon_service.js'
+
+export const ACTIONABLE_NOTIFICATION_TYPES = new Set(['mention', 'pleroma:report', 'follow_request'])
+
+let cachedBadgeUrl = null
+
+export const notificationsFromStore = store => store.state.notifications.data
export const visibleTypes = store => {
- const rootState = store.rootState || store.state
+ // When called from within a module we need rootGetters to access wider scope
+ // however when called from a component (i.e. this.$store) we already have wider scope
+ const rootGetters = store.rootGetters || store.getters
+ const { notificationVisibility } = rootGetters.mergedConfig
return ([
- rootState.config.notificationVisibility.likes && 'like',
- rootState.config.notificationVisibility.mentions && 'mention',
- rootState.config.notificationVisibility.repeats && 'repeat',
- rootState.config.notificationVisibility.follows && 'follow',
- rootState.config.notificationVisibility.followRequest && 'follow_request',
- rootState.config.notificationVisibility.moves && 'move',
- rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
- rootState.config.notificationVisibility.reports && 'pleroma:report',
- rootState.config.notificationVisibility.polls && 'poll'
+ notificationVisibility.likes && 'like',
+ notificationVisibility.mentions && 'mention',
+ notificationVisibility.repeats && 'repeat',
+ notificationVisibility.follows && 'follow',
+ notificationVisibility.followRequest && 'follow_request',
+ notificationVisibility.moves && 'move',
+ notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
+ notificationVisibility.reports && 'pleroma:report',
+ notificationVisibility.polls && 'poll'
].filter(_ => _))
}
-const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction', 'poll']
+const statusNotifications = new Set(['like', 'mention', 'repeat', 'pleroma:emoji_reaction', 'poll'])
-export const isStatusNotification = (type) => includes(statusNotifications, type)
+export const isStatusNotification = (type) => statusNotifications.has(type)
export const isValidNotification = (notification) => {
if (isStatusNotification(notification.type) && !notification.status) {
@@ -49,35 +57,57 @@ const sortById = (a, b) => {
const isMutedNotification = (store, notification) => {
if (!notification.status) return
- return notification.status.muted || muteWordHits(notification.status, store.rootGetters.mergedConfig.muteWords).length > 0
+ const rootGetters = store.rootGetters || store.getters
+ return notification.status.muted || muteWordHits(notification.status, rootGetters.mergedConfig.muteWords).length > 0
}
export const maybeShowNotification = (store, notification) => {
const rootState = store.rootState || store.state
+ const rootGetters = store.rootGetters || store.getters
if (notification.seen) return
if (!visibleTypes(store).includes(notification.type)) return
if (notification.type === 'mention' && isMutedNotification(store, notification)) return
- const notificationObject = prepareNotificationObject(notification, store.rootGetters.i18n)
+ const notificationObject = prepareNotificationObject(notification, rootGetters.i18n)
showDesktopNotification(rootState, notificationObject)
}
export const filteredNotificationsFromStore = (store, types) => {
// map is just to clone the array since sort mutates it and it causes some issues
- let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
- sortedNotifications = sortBy(sortedNotifications, 'seen')
+ const sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
+ // TODO implement sorting elsewhere and make it optional
return sortedNotifications.filter(
(notification) => (types || visibleTypes(store)).includes(notification.type)
)
}
-export const unseenNotificationsFromStore = store =>
- filter(filteredNotificationsFromStore(store), ({ seen }) => !seen)
+export const unseenNotificationsFromStore = store => {
+ const rootGetters = store.rootGetters || store.getters
+ const ignoreInactionableSeen = rootGetters.mergedConfig.ignoreInactionableSeen
+
+ return filteredNotificationsFromStore(store).filter(({ seen, type }) => {
+ if (!ignoreInactionableSeen) return !seen
+ if (seen) return false
+ return ACTIONABLE_NOTIFICATION_TYPES.has(type)
+ })
+}
export const prepareNotificationObject = (notification, i18n) => {
+ if (cachedBadgeUrl === null) {
+ const favicons = FaviconService.getOriginalFavicons()
+ const favicon = favicons[favicons.length - 1]
+ if (!favicon) {
+ cachedBadgeUrl = 'about:blank'
+ } else {
+ cachedBadgeUrl = favicon.favimg.src
+ }
+ }
+
const notifObj = {
- tag: notification.id
+ tag: notification.id,
+ type: notification.type,
+ badge: cachedBadgeUrl
}
const status = notification.status
const title = notification.from_profile.name
@@ -126,15 +156,16 @@ export const prepareNotificationObject = (notification, i18n) => {
}
export const countExtraNotifications = (store) => {
- const mergedConfig = store.getters.mergedConfig
+ const rootGetters = store.rootGetters || store.getters
+ const mergedConfig = rootGetters.mergedConfig
if (!mergedConfig.showExtraNotifications) {
return 0
}
return [
- mergedConfig.showChatsInExtraNotifications ? store.getters.unreadChatCount : 0,
- mergedConfig.showAnnouncementsInExtraNotifications ? store.getters.unreadAnnouncementCount : 0,
- mergedConfig.showFollowRequestsInExtraNotifications ? store.getters.followRequestCount : 0
+ mergedConfig.showChatsInExtraNotifications ? rootGetters.unreadChatCount : 0,
+ mergedConfig.showAnnouncementsInExtraNotifications ? rootGetters.unreadAnnouncementCount : 0,
+ mergedConfig.showFollowRequestsInExtraNotifications ? rootGetters.followRequestCount : 0
].reduce((a, c) => a + c, 0)
}
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index 6c247210..c91a86c8 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
@@ -49,10 +49,14 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
// The normal maxId-check does not tell if older notifications have changed
const notifications = timelineData.data
const readNotifsIds = notifications.filter(n => n.seen).map(n => n.id)
- const numUnseenNotifs = notifications.length - readNotifsIds.length
- if (numUnseenNotifs > 0 && readNotifsIds.length > 0) {
- args.since = Math.max(...readNotifsIds)
- fetchNotifications({ store, args, older })
+ const unreadNotifsIds = notifications.filter(n => !n.seen).map(n => n.id)
+ if (readNotifsIds.length > 0 && readNotifsIds.length > 0) {
+ const minId = Math.min(...unreadNotifsIds) // Oldest known unread notification
+ if (minId !== Infinity) {
+ args.since = false // Don't use since_id since it sorta conflicts with min_id
+ args.minId = minId - 1 // go beyond
+ fetchNotifications({ store, args, older })
+ }
}
return result
diff --git a/src/services/push/push.js b/src/services/sw/sw.js
index 1787ac36..554cc7b8 100644
--- a/src/services/push/push.js
+++ b/src/services/sw/sw.js
@@ -10,8 +10,12 @@ function urlBase64ToUint8Array (base64String) {
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
}
+export function isSWSupported () {
+ return 'serviceWorker' in navigator
+}
+
function isPushSupported () {
- return 'serviceWorker' in navigator && 'PushManager' in window
+ return 'PushManager' in window
}
function getOrCreateServiceWorker () {
@@ -24,7 +28,7 @@ function subscribePush (registration, isEnabled, vapidPublicKey) {
if (!vapidPublicKey) return Promise.reject(new Error('VAPID public key is not found'))
const subscribeOptions = {
- userVisibleOnly: true,
+ userVisibleOnly: false,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
}
return registration.pushManager.subscribe(subscribeOptions)
@@ -39,7 +43,7 @@ function unsubscribePush (registration) {
}
function deleteSubscriptionFromBackEnd (token) {
- return window.fetch('/api/v1/push/subscription/', {
+ return fetch('/api/v1/push/subscription/', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
@@ -78,6 +82,44 @@ function sendSubscriptionToBackEnd (subscription, token, notificationVisibility)
return responseData
})
}
+export async function initServiceWorker (store) {
+ if (!isSWSupported()) return
+ await getOrCreateServiceWorker()
+ navigator.serviceWorker.addEventListener('message', (event) => {
+ const { dispatch } = store
+ const { type, ...rest } = event.data
+
+ switch (type) {
+ case 'notificationClicked':
+ dispatch('notificationClicked', { id: rest.id })
+ }
+ })
+}
+
+export async function showDesktopNotification (content) {
+ if (!isSWSupported) return
+ const { active: sw } = await window.navigator.serviceWorker.getRegistration()
+ if (!sw) return console.error('No serviceworker found!')
+ sw.postMessage({ type: 'desktopNotification', content })
+}
+
+export async function closeDesktopNotification ({ id }) {
+ if (!isSWSupported) return
+ const { active: sw } = await window.navigator.serviceWorker.getRegistration()
+ if (!sw) return console.error('No serviceworker found!')
+ if (id >= 0) {
+ sw.postMessage({ type: 'desktopNotificationClose', content: { id } })
+ } else {
+ sw.postMessage({ type: 'desktopNotificationClose', content: { all: true } })
+ }
+}
+
+export async function updateFocus () {
+ if (!isSWSupported) return
+ const { active: sw } = await window.navigator.serviceWorker.getRegistration()
+ if (!sw) return console.error('No serviceworker found!')
+ sw.postMessage({ type: 'updateFocus' })
+}
export function registerPushNotifications (isEnabled, vapidPublicKey, token, notificationVisibility) {
if (isPushSupported()) {
@@ -98,13 +140,8 @@ export function unregisterPushNotifications (token) {
})
.then(([registration, unsubResult]) => {
if (!unsubResult) {
- console.warn('Push subscription cancellation wasn\'t successful, killing SW anyway...')
+ console.warn('Push subscription cancellation wasn\'t successful')
}
- return registration.unregister().then((result) => {
- if (!result) {
- console.warn('Failed to kill SW')
- }
- })
})
]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
}
diff --git a/src/sw.js b/src/sw.js
index 70fed44b..4ee7b9ff 100644
--- a/src/sw.js
+++ b/src/sw.js
@@ -13,9 +13,10 @@ const i18n = createI18n({
messages
})
-function isEnabled () {
- return localForage.getItem('vuex-lz')
- .then(data => data.config.webPushNotifications)
+const state = {
+ lastFocused: null,
+ notificationIds: new Set(),
+ allowedNotificationTypes: null
}
function getWindowClients () {
@@ -23,17 +24,46 @@ function getWindowClients () {
.then((clientList) => clientList.filter(({ type }) => type === 'window'))
}
-const setLocale = async () => {
- const state = await localForage.getItem('vuex-lz')
- const locale = state.config.interfaceLanguage || 'en'
+const setSettings = async () => {
+ const vuexState = await localForage.getItem('vuex-lz')
+ const locale = vuexState.config.interfaceLanguage || 'en'
i18n.locale = locale
+ const notificationsNativeArray = Object.entries(vuexState.config.notificationNative)
+ state.webPushAlwaysShowNotifications = vuexState.config.webPushAlwaysShowNotifications
+
+ state.allowedNotificationTypes = new Set(
+ notificationsNativeArray
+ .filter(([k, v]) => v)
+ .map(([k]) => {
+ switch (k) {
+ case 'mentions':
+ return 'mention'
+ case 'likes':
+ return 'like'
+ case 'repeats':
+ return 'repeat'
+ case 'emojiReactions':
+ return 'pleroma:emoji_reaction'
+ case 'reports':
+ return 'pleroma:report'
+ case 'followRequest':
+ return 'follow_request'
+ case 'follows':
+ return 'follow'
+ case 'polls':
+ return 'poll'
+ default:
+ return k
+ }
+ })
+ )
}
-const maybeShowNotification = async (event) => {
- const enabled = await isEnabled()
+const showPushNotification = async (event) => {
const activeClients = await getWindowClients()
- await setLocale()
- if (enabled && (activeClients.length === 0)) {
+ await setSettings()
+ // Only show push notifications if all tabs/windows are closed
+ if (state.webPushAlwaysShowNotifications || activeClients.length === 0) {
const data = event.data.json()
const url = `${self.registration.scope}api/v1/notifications/${data.notification_id}`
@@ -43,13 +73,48 @@ const maybeShowNotification = async (event) => {
const res = prepareNotificationObject(parsedNotification, i18n)
- self.registration.showNotification(res.title, res)
+ if (state.webPushAlwaysShowNotifications || state.allowedNotificationTypes.has(parsedNotification.type)) {
+ return self.registration.showNotification(res.title, res)
+ }
}
+ return Promise.resolve()
}
self.addEventListener('push', async (event) => {
if (event.data) {
- event.waitUntil(maybeShowNotification(event))
+ // Supposedly, we HAVE to return a promise inside waitUntil otherwise it will
+ // show (extra) notification that website is updated in background
+ event.waitUntil(showPushNotification(event))
+ }
+})
+
+self.addEventListener('message', async (event) => {
+ await setSettings()
+ const { type, content } = event.data
+
+ if (type === 'desktopNotification') {
+ const { title, ...rest } = content
+ const { tag, type } = rest
+ if (state.notificationIds.has(tag)) return
+ state.notificationIds.add(tag)
+ setTimeout(() => state.notificationIds.delete(tag), 10000)
+ if (state.allowedNotificationTypes.has(type)) {
+ self.registration.showNotification(title, rest)
+ }
+ }
+
+ if (type === 'desktopNotificationClose') {
+ const { id, all } = content
+ const search = all ? null : { tag: id }
+ const notifications = await self.registration.getNotifications(search)
+ notifications.forEach(n => n.close())
+ }
+
+ if (type === 'updateFocus') {
+ state.lastFocused = event.source.id
+
+ const notifications = await self.registration.getNotifications()
+ notifications.forEach(n => n.close())
}
})
@@ -59,7 +124,14 @@ self.addEventListener('notificationclick', (event) => {
event.waitUntil(getWindowClients().then((list) => {
for (let i = 0; i < list.length; i++) {
const client = list[i]
- if (client.url === '/' && 'focus' in client) { return client.focus() }
+ client.postMessage({ type: 'notificationClicked', id: event.notification.tag })
+ }
+
+ for (let i = 0; i < list.length; i++) {
+ const client = list[i]
+ if (state.lastFocused === null || client.id === state.lastFocused) {
+ if ('focus' in client) return client.focus()
+ }
}
if (clients.openWindow) return clients.openWindow('/')
diff --git a/test/unit/specs/modules/statuses.spec.js b/test/unit/specs/modules/statuses.spec.js
index a8d0e5a3..0d83a9f8 100644
--- a/test/unit/specs/modules/statuses.spec.js
+++ b/test/unit/specs/modules/statuses.spec.js
@@ -77,24 +77,6 @@ describe('Statuses module', () => {
expect(state.timelines.public.newStatusCount).to.equal(0)
})
- it('removes statuses by tag on deletion', () => {
- const state = defaultState()
- const status = makeMockStatus({ id: '1' })
- const otherStatus = makeMockStatus({ id: '3' })
- status.uri = 'xxx'
- const deletion = makeMockStatus({ id: '2', type: 'deletion' })
- deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
- deletion.uri = 'xxx'
-
- mutations.addNewStatuses(state, { statuses: [status, otherStatus], showImmediately: true, timeline: 'public' })
- mutations.addNewStatuses(state, { statuses: [deletion], showImmediately: true, timeline: 'public' })
-
- expect(state.allStatuses).to.eql([otherStatus])
- expect(state.timelines.public.statuses).to.eql([otherStatus])
- expect(state.timelines.public.visibleStatuses).to.eql([otherStatus])
- expect(state.timelines.public.maxId).to.eql('3')
- })
-
it('does not update the maxId when the noIdUpdate flag is set', () => {
const state = defaultState()
const status = makeMockStatus({ id: '1' })
@@ -315,62 +297,4 @@ describe('Statuses module', () => {
expect(state.timelines.user.userId).to.eql(123)
})
})
-
- describe('notifications', () => {
- it('removes a notification when the notice gets removed', () => {
- const user = { id: '1' }
- const state = defaultState()
- const status = makeMockStatus({ id: '1' })
- const otherStatus = makeMockStatus({ id: '3' })
- const mentionedStatus = makeMockStatus({ id: '2' })
- mentionedStatus.attentions = [user]
- mentionedStatus.uri = 'xxx'
- otherStatus.attentions = [user]
-
- const deletion = makeMockStatus({ id: '4', type: 'deletion' })
- deletion.text = 'Dolus deleted notice {{tag:gs.smuglo.li,2016-11-18:noticeId=1038007:objectType=note}}.'
- deletion.uri = 'xxx'
- const newNotificationSideEffects = () => {}
- mutations.addNewStatuses(state, { statuses: [status, otherStatus], user })
- mutations.addNewNotifications(
- state,
- {
- notifications: [{
- from_profile: { id: '2' },
- id: '998',
- type: 'mention',
- status: otherStatus,
- action: otherStatus,
- seen: false
- }],
- newNotificationSideEffects
- })
-
- expect(state.notifications.data.length).to.eql(1)
- mutations.addNewNotifications(
- state,
- {
- notifications: [{
- from_profile: { id: '2' },
- id: '999',
- type: 'mention',
- status: mentionedStatus,
- action: mentionedStatus,
- seen: false
- }],
- newNotificationSideEffects
- })
-
- mutations.addNewStatuses(state, { statuses: [mentionedStatus], user })
- expect(state.allStatuses.length).to.eql(3)
- expect(state.notifications.data.length).to.eql(2)
- expect(state.notifications.data[1].status).to.eql(mentionedStatus)
- expect(state.notifications.data[1].action).to.eql(mentionedStatus)
- expect(state.notifications.data[1].type).to.eql('mention')
-
- mutations.addNewStatuses(state, { statuses: [deletion], user })
- expect(state.allStatuses.length).to.eql(2)
- expect(state.notifications.data.length).to.eql(1)
- })
- })
})
diff --git a/test/unit/specs/services/notification_utils/notification_utils.spec.js b/test/unit/specs/services/notification_utils/notification_utils.spec.js
index 00628d83..60db7380 100644
--- a/test/unit/specs/services/notification_utils/notification_utils.spec.js
+++ b/test/unit/specs/services/notification_utils/notification_utils.spec.js
@@ -5,28 +5,28 @@ describe('NotificationUtils', () => {
it('should return sorted notifications with configured types', () => {
const store = {
state: {
- statuses: {
- notifications: {
- data: [
- {
- id: 1,
- action: { id: '1' },
- type: 'like'
- },
- {
- id: 2,
- action: { id: '2' },
- type: 'mention'
- },
- {
- id: 3,
- action: { id: '3' },
- type: 'repeat'
- }
- ]
- }
- },
- config: {
+ notifications: {
+ data: [
+ {
+ id: 1,
+ action: { id: '1' },
+ type: 'like'
+ },
+ {
+ id: 2,
+ action: { id: '2' },
+ type: 'mention'
+ },
+ {
+ id: 3,
+ action: { id: '3' },
+ type: 'repeat'
+ }
+ ]
+ }
+ },
+ getters: {
+ mergedConfig: {
notificationVisibility: {
likes: true,
repeats: true,
@@ -55,23 +55,23 @@ describe('NotificationUtils', () => {
it('should return only notifications not marked as seen', () => {
const store = {
state: {
- statuses: {
- notifications: {
- data: [
- {
- action: { id: '1' },
- type: 'like',
- seen: false
- },
- {
- action: { id: '2' },
- type: 'mention',
- seen: true
- }
- ]
- }
- },
- config: {
+ notifications: {
+ data: [
+ {
+ action: { id: '1' },
+ type: 'like',
+ seen: false
+ },
+ {
+ action: { id: '2' },
+ type: 'mention',
+ seen: true
+ }
+ ]
+ }
+ },
+ getters: {
+ mergedConfig: {
notificationVisibility: {
likes: true,
repeats: true,