aboutsummaryrefslogtreecommitdiff
path: root/src/components/notifications
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/notifications')
-rw-r--r--src/components/notifications/notification_filters.vue35
-rw-r--r--src/components/notifications/notifications.js74
-rw-r--r--src/components/notifications/notifications.scss44
-rw-r--r--src/components/notifications/notifications.vue141
4 files changed, 189 insertions, 105 deletions
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
index ba0e90a0..1315b51a 100644
--- a/src/components/notifications/notification_filters.vue
+++ b/src/components/notifications/notification_filters.vue
@@ -5,7 +5,7 @@
placement="bottom"
:bound-to="{ x: 'container' }"
>
- <template v-slot:content>
+ <template #content>
<div class="dropdown-menu">
<button
class="button-default dropdown-item"
@@ -61,10 +61,19 @@
:class="{ 'menu-checkbox-checked': filters.moves }"
/>{{ $t('settings.notification_visibility_moves') }}
</button>
+ <button
+ class="button-default dropdown-item"
+ @click="toggleNotificationFilter('polls')"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.polls }"
+ />{{ $t('settings.notification_visibility_polls') }}
+ </button>
</div>
</template>
- <template v-slot:trigger>
- <button class="button-unstyled">
+ <template #trigger>
+ <button class="filter-trigger-button button-unstyled">
<FAIcon icon="filter" />
</button>
</template>
@@ -100,23 +109,3 @@ export default {
}
}
</script>
-
-<style lang="scss">
-
-.NotificationFilters {
- align-self: stretch;
-
- > button {
- font-size: 1.2em;
- padding-left: 0.7em;
- padding-right: 0.2em;
- line-height: 100%;
- height: 100%;
- }
-
- .dropdown-item {
- margin: 0;
- }
-}
-
-</style>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index c8f1ebcb..d499d3d6 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -1,3 +1,4 @@
+import { computed } from 'vue'
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue'
import NotificationFilters from './notification_filters.vue'
@@ -9,10 +10,12 @@ import {
} from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
+import { faCircleNotch, faArrowUp, faMinus } from '@fortawesome/free-solid-svg-icons'
library.add(
- faCircleNotch
+ faCircleNotch,
+ faArrowUp,
+ faMinus
)
const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
@@ -23,16 +26,17 @@ const Notifications = {
NotificationFilters
},
props: {
- // Disables display of panel header
- noHeading: Boolean,
// Disables panel styles, unread mark, potentially other notification-related actions
// meant for "Interactions" timeline
minimalMode: Boolean,
// Custom filter mode, an array of strings, possible values 'mention', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
- filterMode: Array
+ filterMode: Array,
+ // Disable teleporting (i.e. for /users/user/notifications)
+ disableTeleport: Boolean
},
data () {
return {
+ showScrollTop: false,
bottomedOut: false,
// How many seen notifications to display in the list. The more there are,
// the heavier the page becomes. This count is increased when loading
@@ -40,6 +44,11 @@ const Notifications = {
seenToDisplayCount: DEFAULT_SEEN_TO_DISPLAY_COUNT
}
},
+ provide () {
+ return {
+ popoversZLayer: computed(() => this.popoversZLayer)
+ }
+ },
computed: {
mainClass () {
return this.minimalMode ? '' : 'panel panel-default'
@@ -60,15 +69,46 @@ const Notifications = {
return this.unseenNotifications.length
},
unseenCountTitle () {
- return this.unseenCount + (this.unreadChatCount)
+ return this.unseenCount + (this.unreadChatCount) + this.unreadAnnouncementCount
},
loading () {
return this.$store.state.statuses.notifications.loading
},
+ noHeading () {
+ const { layoutType } = this.$store.state.interface
+ return this.minimalMode || layoutType === 'mobile'
+ },
+ teleportTarget () {
+ const { layoutType } = this.$store.state.interface
+ const map = {
+ wide: '#notifs-column',
+ mobile: '#mobile-notifications'
+ }
+ return map[layoutType] || '#notifs-sidebar'
+ },
+ popoversZLayer () {
+ const { layoutType } = this.$store.state.interface
+ return layoutType === 'mobile' ? 'navbar' : null
+ },
notificationsToDisplay () {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
- ...mapGetters(['unreadChatCount'])
+ noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
+ },
+ mounted () {
+ this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
+ if (!this.scrollerRef) {
+ this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
+ }
+ if (!this.scrollerRef) {
+ this.scrollerRef = this.$refs.root.closest('.column.main')
+ }
+ this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
+ },
+ unmounted () {
+ if (!this.scrollerRef) return
+ this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
},
watch: {
unseenCountTitle (count) {
@@ -79,9 +119,29 @@ const Notifications = {
FaviconService.clearFaviconBadge()
this.$store.dispatch('setPageTitle', '')
}
+ },
+ teleportTarget () {
+ // handle scroller change
+ this.$nextTick(() => {
+ this.scrollerRef.removeEventListener('scroll', this.updateScrollPosition)
+ this.scrollerRef = this.$refs.root.closest('.column.-scrollable')
+ if (!this.scrollerRef) {
+ this.scrollerRef = this.$refs.root.closest('.mobile-notifications')
+ }
+ this.scrollerRef.addEventListener('scroll', this.updateScrollPosition)
+ this.updateScrollPosition()
+ })
}
},
methods: {
+ 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
+ },
markAsSeen () {
this.$store.dispatch('markNotificationsAsSeen')
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 77b3c438..9b241565 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -11,10 +11,6 @@
color: var(--text, $fallback--text);
}
- .notifications-footer {
- border: none;
- }
-
.notification {
position: relative;
@@ -37,11 +33,6 @@
.notification {
box-sizing: border-box;
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- word-wrap: break-word;
- word-break: break-word;
&:hover .animated.Avatar {
canvas {
@@ -52,6 +43,10 @@
}
}
+ &:last-child .Notification {
+ border-bottom: none;
+ }
+
.non-mention {
display: flex;
flex: 1;
@@ -64,13 +59,13 @@
height: 32px;
}
- --link: var(--faintLink);
- --text: var(--faint);
+ .faint {
+ --link: var(--faintLink);
+ --text: var(--faint);
+ }
}
.follow-request-accept {
- cursor: pointer;
-
&:hover {
color: $fallback--text;
color: var(--text, $fallback--text);
@@ -78,8 +73,6 @@
}
.follow-request-reject {
- cursor: pointer;
-
&:hover {
color: $fallback--cRed;
color: var(--cRed, $fallback--cRed);
@@ -119,16 +112,26 @@
min-width: 3em;
text-align: right;
}
+
+ .timeago-link {
+ margin-right: 0.2em;
+ }
+
+ .expand-icon {
+ .svg-inline--fa {
+ margin-left: 0.25em;
+ }
+ }
}
.emoji-reaction-emoji {
- font-size: 16px;
+ font-size: 1.3em;
}
.notification-details {
- min-width: 0px;
+ min-width: 0;
word-wrap: break-word;
- line-height:18px;
+ line-height: var(--post-line-height);
position: relative;
overflow: hidden;
width: 100%;
@@ -151,7 +154,7 @@
}
.timeago {
- margin-right: .2em;
+ margin-right: 0.2em;
}
.status-content {
@@ -164,7 +167,8 @@
margin: 0 0 0.3em;
padding: 0;
font-size: 1em;
- line-height:20px;
+ line-height: 1.5;
+
small {
font-weight: lighter;
}
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 2ce5d56f..633efca6 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -1,69 +1,100 @@
<template>
- <div
- :class="{ minimal: minimalMode }"
- class="Notifications"
+ <teleport
+ :disabled="minimalMode || disableTeleport"
+ :to="teleportTarget"
>
- <div :class="mainClass">
- <div
- v-if="!noHeading"
- class="panel-heading"
- >
- <div class="title">
- {{ $t('notifications.notifications') }}
- <span
- v-if="unseenCount"
- class="badge badge-notification unseen-count"
- >{{ unseenCount }}</span>
- </div>
- <button
- v-if="unseenCount"
- class="button-default read-button"
- @click.prevent="markAsSeen"
- >
- {{ $t('notifications.read') }}
- </button>
- <NotificationFilters />
- </div>
- <div class="panel-body">
+ <component
+ :is="noHeading ? 'div' : 'aside'"
+ ref="root"
+ :class="{ minimal: minimalMode }"
+ class="Notifications"
+ >
+ <div :class="mainClass">
<div
- v-for="notification in notificationsToDisplay"
- :key="notification.id"
- class="notification"
- :class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
+ v-if="!noHeading"
+ class="notifications-heading panel-heading -sticky"
>
- <div class="notification-overlay" />
- <notification :notification="notification" />
+ <div class="title">
+ {{ $t('notifications.notifications') }}
+ <span
+ v-if="unseenCount"
+ class="badge badge-notification unseen-count"
+ >{{ unseenCount }}</span>
+ </div>
+ <div
+ v-if="showScrollTop"
+ class="rightside-button"
+ >
+ <button
+ class="button-unstyled scroll-to-top-button"
+ type="button"
+ :title="$t('general.scroll_to_top')"
+ @click="scrollToTop"
+ >
+ <FALayers class="fa-scale-110 fa-old-padding-layer">
+ <FAIcon icon="arrow-up" />
+ <FAIcon
+ icon="minus"
+ transform="up-7"
+ />
+ </FALayers>
+ </button>
+ </div>
+ <button
+ v-if="unseenCount"
+ class="button-default read-button"
+ type="button"
+ @click.prevent="markAsSeen"
+ >
+ {{ $t('notifications.read') }}
+ </button>
+ <NotificationFilters class="rightside-button" />
</div>
- </div>
- <div class="panel-footer notifications-footer">
<div
- v-if="bottomedOut"
- class="new-status-notification text-center faint"
+ class="panel-body"
+ role="feed"
>
- {{ $t('notifications.no_more_notifications') }}
+ <div
+ v-for="notification in notificationsToDisplay"
+ :key="notification.id"
+ role="listitem"
+ class="notification"
+ :class="{unseen: !minimalMode && !notification.seen}"
+ >
+ <div class="notification-overlay" />
+ <notification :notification="notification" />
+ </div>
</div>
- <button
- v-else-if="!loading"
- class="button-unstyled -link -fullwidth"
- @click.prevent="fetchOlderNotifications()"
- >
- <div class="new-status-notification text-center">
- {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
+ <div class="panel-footer">
+ <div
+ v-if="bottomedOut"
+ class="new-status-notification text-center faint"
+ >
+ {{ $t('notifications.no_more_notifications') }}
+ </div>
+ <button
+ v-else-if="!loading"
+ class="button-unstyled -link -fullwidth"
+ @click.prevent="fetchOlderNotifications()"
+ >
+ <div class="new-status-notification text-center">
+ {{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older') }}
+ </div>
+ </button>
+ <div
+ v-else
+ class="new-status-notification text-center"
+ >
+ <FAIcon
+ icon="circle-notch"
+ spin
+ size="lg"
+ />
</div>
- </button>
- <div
- v-else
- class="new-status-notification text-center"
- >
- <FAIcon
- icon="circle-notch"
- spin
- size="lg"
- />
</div>
</div>
- </div>
- </div>
+ </component>
+ </teleport>
</template>
<script src="./notifications.js"></script>