forked from kaverti/website
479 lines
11 KiB
Vue
479 lines
11 KiB
Vue
<template>
|
|
<main>
|
|
<div class="column">
|
|
<div class="box has-text-centered">
|
|
<div class="columns">
|
|
<div class='column'>
|
|
<div>
|
|
<h1>Notifications</h1>
|
|
<p>Optimized for mobile</p><br>
|
|
<div class='box'>
|
|
<div
|
|
:key='"notification-" + index'
|
|
v-for='(notification, index) in postNotifications'
|
|
class='notification_button__menu__item'
|
|
:class='{
|
|
"notification_button__menu__item--uninteracted": !notification.interacted,
|
|
"notification_button__menu__item--no_border": index > 2
|
|
}'
|
|
|
|
@click='click(notification)'
|
|
>
|
|
|
|
<div class='notification_button__menu__item__header'>
|
|
<span v-if='notification.type === "mention"'>New mention</span>
|
|
<span v-else-if='notification.type === "reply"'>Post reply</span>
|
|
<span>
|
|
<span class='notification_button__menu__item__header__date'>{{notification.createdAt | formatDate }}</span>
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span v-if='isYouOrDeleted(notification.PostNotification.User)'>
|
|
{{ notification.PostNotification.User ? 'You' : '[deleted]' }}
|
|
</span>
|
|
<span class='notification_button__menu__item__link' v-else>
|
|
{{notification.PostNotification.User.username}}
|
|
</span>
|
|
|
|
wrote
|
|
"{{notification.PostNotification.Post.content | stripTags | truncate(50)}}"
|
|
</div>
|
|
<b-button
|
|
@click.stop='deleteNotification(notification.id)'
|
|
>Close</b-button>
|
|
|
|
</div>
|
|
<div v-if='!notifications.length'>
|
|
<h1><i class="fal fa-bell"></i></h1>
|
|
No notifications, for now!
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</template>
|
|
|
|
<script>
|
|
import AjaxErrorHandler from '../../assets/js/errorHandler'
|
|
|
|
export default {
|
|
name: 'NotificationButton',
|
|
data () {
|
|
return {
|
|
unreadCount: 0,
|
|
notifications: [],
|
|
|
|
showMenu: true,
|
|
shake: false
|
|
}
|
|
},
|
|
computed: {
|
|
unreadCountText () {
|
|
if(this.unreadCount > 99) {
|
|
return '99+'
|
|
} else {
|
|
return this.unreadCount
|
|
}
|
|
},
|
|
postNotifications () {
|
|
return this.notifications.filter(n => n.PostNotification.Post);
|
|
}
|
|
},
|
|
methods: {
|
|
isYouOrDeleted (user) {
|
|
return !user || user.username === this.$store.state.username
|
|
},
|
|
setShowMenu (val) {
|
|
this.showMenu = val
|
|
|
|
if(val) {
|
|
this.resetUnreadCount()
|
|
} else {
|
|
setTimeout(() => {
|
|
this.emojiIndex++
|
|
}, 200)
|
|
}
|
|
},
|
|
getIndexById (id) {
|
|
let index
|
|
|
|
this.notifications.forEach((notification, i) => {
|
|
if(notification.id === id) {
|
|
index = i
|
|
}
|
|
})
|
|
|
|
return index
|
|
},
|
|
getNotifications () {
|
|
this.axios
|
|
.get('/api/v1/users/notification')
|
|
.then(res => {
|
|
this.notifications = res.data.Notifications
|
|
this.unreadCount = res.data.unreadCount
|
|
})
|
|
.catch(e => {
|
|
this.showConn(e)
|
|
})
|
|
},
|
|
resetUnreadCount () {
|
|
this.axios
|
|
.put('/api/v1/users/notification')
|
|
.then(() => {
|
|
this.unreadCount = 0
|
|
})
|
|
.catch(AjaxErrorHandler(this.$store))
|
|
},
|
|
deleteNotification (id) {
|
|
let index = this.getIndexById(id)
|
|
|
|
this.axios
|
|
.delete('/api/v1/users/notification/' + id)
|
|
.then(() => {
|
|
this.notifications.splice(index, 1)
|
|
})
|
|
.catch(AjaxErrorHandler(this.$store))
|
|
},
|
|
setInteracted (id) {
|
|
let index = this.getIndexById(id)
|
|
let item = this.notifications[index]
|
|
|
|
this.axios
|
|
.put('/api/v1/users/notification/' + id)
|
|
.then(() => {
|
|
this.$set(
|
|
this.notifications,
|
|
index,
|
|
Object.assign(item, { interacted: true })
|
|
)
|
|
})
|
|
.catch(AjaxErrorHandler(this.$store))
|
|
},
|
|
click (notification) {
|
|
if(!notification.interacted) {
|
|
this.setInteracted(notification.id)
|
|
}
|
|
|
|
if(notification.type === 'mention' || notification.type === 'reply') {
|
|
this.$router.push('/p/' + notification.PostNotification.Post.id)
|
|
} else if(notification.type === 'reply') {
|
|
this.$router.push('/p/' + notification.PostNotification.Post.id)
|
|
}
|
|
|
|
this.setShowMenu(false)
|
|
}
|
|
},
|
|
created () {
|
|
if(this.$store.state.username) this.getNotifications()
|
|
|
|
this.$socket.on('notification', notification => {
|
|
this.unreadCount++
|
|
this.notifications.unshift(notification)
|
|
|
|
this.shake = true
|
|
setTimeout(() => {
|
|
this.shake = false
|
|
}, 1000)
|
|
})
|
|
},
|
|
watch: {
|
|
'$store.state.username': 'getNotifications'
|
|
},
|
|
beforeRouteEnter (to, from, next) {
|
|
next(vm => {
|
|
if(!vm.$store.state.username) {
|
|
vm.$store.commit('setAccountModalState', true);
|
|
next('/')
|
|
}
|
|
})
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang='scss' scoped>
|
|
@import '../../assets/scss/variables.scss';
|
|
|
|
@keyframes shake {
|
|
0% {
|
|
position: relative;
|
|
left: 0;
|
|
}
|
|
25% {
|
|
position: relative;
|
|
left: -1rem;
|
|
}
|
|
75% {
|
|
position: relative;
|
|
left: 1rem;
|
|
}
|
|
100% {
|
|
left: 0rem;
|
|
}
|
|
}
|
|
|
|
.notification_button {
|
|
position: relative;
|
|
|
|
@at-root #{&}__overlay {
|
|
width: 100%;
|
|
height: 100%;
|
|
top: 0;
|
|
left: 0;
|
|
position: fixed;
|
|
z-index: 5;
|
|
pointer-events: none;
|
|
|
|
@at-root #{&}--show {
|
|
pointer-events: all;
|
|
}
|
|
}
|
|
|
|
@at-root #{&}__menu_group {
|
|
position: relative;
|
|
top: -3rem;
|
|
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.2s, top 0.2s;
|
|
|
|
@at-root #{&}--show {
|
|
pointer-events: all;
|
|
opacity: 1;
|
|
top: -2.5rem;
|
|
}
|
|
}
|
|
|
|
@at-root #{&}__triangle {
|
|
width: 1rem;
|
|
height: 1rem;
|
|
background-color: #fafafa;
|
|
transform: rotate(45deg);
|
|
position: absolute;
|
|
top: 40px;
|
|
border-radius: 0.125rem 0 0 0;
|
|
border: 1.5px solid $color__gray--darkest;
|
|
left: calc(50% - 1rem /2);
|
|
clip-path: polygon(0 0, 100% 0%, 0 100%);
|
|
z-index: 8;
|
|
}
|
|
@at-root #{&}__menu {
|
|
left: calc(-50% - 1.25rem);
|
|
position: absolute;
|
|
top: 2.9rem;
|
|
background-color: #fafafa;
|
|
width: 20rem;
|
|
border-radius: 0.25rem;
|
|
border: 1.5px solid $color__gray--darkest;
|
|
box-shadow: 0 0.25rem 1rem rgba(#000, 0.125);
|
|
min-height: 8rem;
|
|
max-height: 15rem;
|
|
overflow-y: auto;
|
|
z-index: 7;
|
|
|
|
@at-root #{&}__empty {
|
|
background-color: #fafafa;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 2rem;
|
|
height: 8rem;
|
|
justify-content: center;
|
|
font-size: 1rem;
|
|
user-select: none;
|
|
cursor: default;
|
|
transition: none;
|
|
color: $color__gray--darkest;
|
|
|
|
span {
|
|
font-size: 2rem;
|
|
color: $color__gray--darker;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
}
|
|
|
|
@at-root #{&}__item {
|
|
@at-root #{&}--no_border:last-child {
|
|
border: none;
|
|
}
|
|
|
|
padding: 0.5rem;
|
|
border-bottom: thin solid $color__gray--primary;
|
|
cursor: default;
|
|
background-color: #fff;
|
|
|
|
transition: background-color 0.2s;
|
|
|
|
&:hover {
|
|
background-color: $color__lightgray--primary;
|
|
}
|
|
|
|
@at-root #{&}__link {
|
|
font-weight: 400;
|
|
cursor: pointer;
|
|
}
|
|
|
|
@at-root #{&}__header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 0.9rem;
|
|
|
|
@at-root #{&}__date {
|
|
color: $color__text--secondary;
|
|
}
|
|
@at-root #{&}__close {
|
|
background-color: $color__gray--darkest;
|
|
height: 0.9rem;
|
|
width: 0.9rem;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
border-radius: 100%;
|
|
margin-left: 0.25rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
color: #fff;
|
|
position: relative;
|
|
top: 0.0625rem;
|
|
line-height: 1;
|
|
transition: all 0.2s;
|
|
|
|
&:hover {
|
|
filter: brightness(0.9);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@at-root #{&}__button {
|
|
position: relative;
|
|
height: 2.5rem;
|
|
width: 2.5rem;
|
|
transition: border 0.4s, padding 0.4s;
|
|
|
|
@at-root #{&}--shake {
|
|
animation-name: shake;
|
|
animation-iteration-count: 4;
|
|
animation-duration: 0.25s;
|
|
animation-timing-function: ease-in-out;
|
|
}
|
|
|
|
@at-root #{&}__icon {
|
|
font-size: 1.5rem;
|
|
position: relative;
|
|
top: -0.125rem;
|
|
}
|
|
|
|
@at-root #{&}__count {
|
|
position: absolute;
|
|
background-color: $color__blue--primary;
|
|
line-height: 1;
|
|
margin-left: 0.25rem;
|
|
color: #fff;
|
|
border-radius: 100%;
|
|
height: 1rem;
|
|
width: 1rem;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.75rem;
|
|
font-size: 0.9rem;
|
|
justify-content: center;
|
|
left: 0.8rem;
|
|
top: -0.2rem;
|
|
|
|
transition: all 0.2s;
|
|
|
|
@at-root #{&}--none {
|
|
opacity: 0;
|
|
}
|
|
@at-root #{&}--two_figure {
|
|
font-size: 0.8rem;
|
|
}
|
|
@at-root #{&}--three_figure {
|
|
font-size: 0.7rem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.notification_button__menu_group {
|
|
left: calc(3.5rem - 100vw);
|
|
width: calc(100vw - 0.25rem);
|
|
}
|
|
.notification_button__menu {
|
|
width: 100%;
|
|
left: unset;
|
|
right: unset ;
|
|
|
|
}
|
|
}
|
|
|
|
@media (max-width: 870px) {
|
|
//Because the notification button is
|
|
//actually a child of the hamburger menu
|
|
//it 'pops up' when the overlay is showing
|
|
//so we cover it with its own overlay
|
|
//hacky but it works...
|
|
.notification_button__button::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
width: 100%;
|
|
border-radius: 0.25rem;
|
|
height: 100%;
|
|
background-color: hsla(215, 13%, 25%, 0.5);
|
|
transition: all 0.4s;
|
|
}
|
|
.header__group--show .notification_button {
|
|
cursor: default;
|
|
pointer-events: none;
|
|
|
|
@at-root #{&}__button {
|
|
border: none;
|
|
|
|
&::before {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.notification_button {
|
|
position: fixed;
|
|
right: 0.5rem;
|
|
width: 2.4rem;
|
|
top: 0.5rem;
|
|
border-radius: 0.25rem;
|
|
|
|
@at-root #{&}__button {
|
|
border: none;
|
|
}
|
|
|
|
@at-root #{&}__menu_group {
|
|
left: calc(3.5rem - 100vw);
|
|
width: calc(100vw - 0.25rem);
|
|
}
|
|
|
|
@at-root #{&}__menu {
|
|
left: unset;
|
|
right: 0.5rem;
|
|
|
|
@at-root #{&}__empty {
|
|
font-weight: normal;
|
|
}
|
|
}
|
|
|
|
@at-root #{&}__triangle {
|
|
left: unset;
|
|
right: 1.55rem;
|
|
}
|
|
}
|
|
}
|
|
</style>
|