Commit 85c058e9 authored by Shpuld Shpludson's avatar Shpuld Shpludson

New routes, notifications, other impovements in side drwaer

parent 47520818
......@@ -7,6 +7,7 @@ import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ChatPanel from './components/chat_panel/chat_panel.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import { unseenNotificationsFromStore } from './services/notification_utils/notification_utils'
export default {
name: 'app',
......@@ -75,12 +76,15 @@ export default {
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel }
showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel },
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
unseenNotificationsCount () {
return this.unseenNotifications.length
}
},
methods: {
activatePanel (panelName) {
this.mobileActivePanel = panelName
},
scrollToTop () {
window.scrollTo(0, 0)
},
......
......@@ -473,6 +473,24 @@ nav {
}
}
.menu-button {
display: none;
position: relative;
}
.alert-dot {
border-radius: 100%;
height: 8px;
width: 8px;
position: absolute;
left: calc(50% - 4px);
top: calc(50% - 4px);
margin-left: 6px;
margin-top: -6px;
background-color: $fallback--cRed;
background-color: var(--badgeNotification, $fallback--cRed);
}
.fade-enter-active, .fade-leave-active {
transition: opacity .2s
}
......@@ -524,9 +542,6 @@ nav {
.back-button {
display: none;
}
.site-name {
padding-left: 20px;
}
}
.sidebar-bounds {
......@@ -665,4 +680,9 @@ nav {
max-width: 4em;
}
}
.menu-button {
display: block;
margin-right: 0.8em;
}
}
......@@ -7,50 +7,45 @@
</div>
<div class='inner-nav'>
<div class='item'>
<router-link class="back-button" @click.native="activatePanel('timeline')" :to="{ name: 'root' }" active-class="hidden">
<a href="#" class="menu-button" @click.stop.prevent="toggleMobileSidebar()">
<i class="button-icon icon-menu"></i>
<div class="alert-dot" v-if="unseenNotificationsCount"></div>
</a>
<!-- <router-link class="back-button" @click.native="activatePanel('timeline')" :to="{ name: 'root' }" active-class="hidden">
<i class="icon-left-open" :title="$t('nav.back')"></i>
</router-link>
</router-link> -->
<router-link class="site-name" :to="{ name: 'root' }" active-class="home">{{sitename}}</router-link>
</div>
<div class='item right'>
<a href="#" @click.stop.prevent="toggleMobileSidebar()"><i class="button-icon icon-menu"></i></a>
<user-finder class="button-icon nav-icon mobile-hidden" @toggled="onFinderToggled"></user-finder>
<router-link class="mobile-hidden" @click.native="activatePanel('timeline')" :to="{ name: 'settings'}"><i class="button-icon icon-cog nav-icon" :title="$t('nav.preferences')"></i></router-link>
<router-link class="mobile-hidden" :to="{ name: 'settings'}"><i class="button-icon icon-cog nav-icon" :title="$t('nav.preferences')"></i></router-link>
<a href="#" class="mobile-hidden" v-if="currentUser" @click.prevent="logout"><i class="button-icon icon-logout nav-icon" :title="$t('login.logout')"></i></a>
</div>
</div>
</nav>
<div v-if="" class="container" id="content">
<side-drawer
ref="sideDrawer"
:activatePanel="activatePanel"
:logout="logout"
>
</side-drawer>
<div class="sidebar-flexer">
<side-drawer ref="sideDrawer" :logout="logout"></side-drawer>
<div class="sidebar-flexer mobile-hidden">
<div class="sidebar-bounds">
<div class="sidebar-scroller">
<div class="sidebar">
<user-panel :activatePanel="activatePanel" :class="mobileShowOnlyIn('poststatus')"></user-panel>
<nav-panel :activatePanel="activatePanel" class="mobile-hidden"></nav-panel>
<instance-specific-panel v-if="showInstanceSpecificPanel" class="mobile-hidden"></instance-specific-panel>
<user-panel></user-panel>
<nav-panel></nav-panel>
<instance-specific-panel v-if="showInstanceSpecificPanel"></instance-specific-panel>
<features-panel v-if="!currentUser"></features-panel>
<who-to-follow-panel v-if="currentUser && suggestionsEnabled"></who-to-follow-panel>
<notifications :activatePanel="activatePanel" v-if="currentUser" :class="mobileShowOnlyIn('notifications')"></notifications>
<notifications v-if="currentUser"></notifications>
</div>
</div>
</div>
</div>
<div class="main" :class="{ 'mobile-hidden': mobileActivePanel != 'timeline' }">
<div class="main">
<transition name="fade">
<router-view></router-view>
</transition>
</div>
</div>
<chat-panel v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
<chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
</div>
</template>
......
......@@ -12,6 +12,10 @@ import UserSettings from 'components/user_settings/user_settings.vue'
import FollowRequests from 'components/follow_requests/follow_requests.vue'
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
import UserSearch from 'components/user_search/user_search.vue'
import Notifications from 'components/notifications/notifications.vue'
import UserPanel from 'components/user_panel/user_panel.vue'
import LoginForm from 'components/login_form/login_form.vue'
import ChatPanel from 'components/chat_panel/chat_panel.vue'
export default (store) => {
return [
......@@ -62,6 +66,10 @@ export default (store) => {
{ name: 'friend-requests', path: '/~/friend-requests', component: FollowRequests },
{ name: 'user-settings', path: '/~/user-settings', component: UserSettings },
{ name: 'oauth-callback', path: '/~/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
{ name: 'user-search', path: '/~/user-search', component: UserSearch, props: (route) => ({ query: route.query.query }) }
{ name: 'user-search', path: '/~/user-search', component: UserSearch, props: (route) => ({ query: route.query.query }) },
{ name: 'notifications', path: '/:username/notifications', component: Notifications },
{ name: 'new-status', path: '/:username/new-status', component: UserPanel },
{ name: 'login', path: '/~/login', component: LoginForm },
{ name: 'chat', path: '/~/chat', component: ChatPanel, props: () => ({ floating: false }) }
]
}
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const chatPanel = {
props: [ 'floating' ],
data () {
return {
currentMessage: '',
......@@ -22,7 +23,7 @@ const chatPanel = {
this.collapsed = !this.collapsed
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name)
return generateProfileLink(user.id, user.username)
}
}
}
......
<template>
<div class="chat-panel" v-if="!this.collapsed">
<div class="chat-panel" v-if="!this.collapsed || !this.floating">
<div class="panel panel-default">
<div class="panel-heading timeline-heading chat-heading" @click.stop.prevent="togglePanel">
<div class="panel-heading timeline-heading" :class="{ 'chat-heading': floating }" @click.stop.prevent="togglePanel">
<div class="title">
{{$t('chat.title')}}
<i class="icon-cancel" style="float: right;"></i>
<i class="icon-cancel" style="float: right;" v-if="floating"></i>
</div>
</div>
<div class="chat-window" v-chat-scroll>
......@@ -52,6 +52,7 @@
right: 0px;
bottom: 0px;
z-index: 1000;
max-width: 25em;
}
.chat-heading {
......@@ -63,10 +64,13 @@
}
.chat-window {
width: 345px;
max-height: 40vh;
overflow-y: auto;
overflow-x: hidden;
max-height: 20em;
}
.chat-window-container {
height: 100%;
}
.chat-message {
......
const NavPanel = {
props: [ 'activatePanel' ],
computed: {
currentUser () {
return this.$store.state.users.currentUser
......
......@@ -3,32 +3,32 @@
<div class="panel panel-default">
<ul>
<li v-if='currentUser'>
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'friends' }">
<router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if='currentUser'>
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
<router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
{{ $t("nav.mentions") }}
</router-link>
</li>
<li v-if='currentUser'>
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }}
</router-link>
</li>
<li v-if='currentUser && currentUser.locked'>
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'friend-requests' }">
<router-link :to="{ name: 'friend-requests' }">
{{ $t("nav.friend_requests") }}
</router-link>
</li>
<li>
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'public-timeline' }">
<router-link :to="{ name: 'public-timeline' }">
{{ $t("nav.public_tl") }}
</router-link>
</li>
<li>
<router-link @click.native="activatePanel('timeline')" :to="{ name: 'public-external-timeline' }">
<router-link :to="{ name: 'public-external-timeline' }">
{{ $t("nav.twkn") }}
</router-link>
</li>
......
......@@ -11,10 +11,7 @@ const Notification = {
betterShadow: this.$store.state.interface.browserSupport.cssFilter
}
},
props: [
'notification',
'activatePanel'
],
props: [ 'notification' ],
components: {
Status, StillImage, UserCardContent
},
......
<template>
<status :activatePanel="activatePanel" v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status>
<status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status>
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]"v-else>
<a class='avatar-container' :href="notification.action.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
<StillImage class='avatar-compact' :class="{'better-shadow': betterShadow}" :src="notification.action.user.profile_image_url_original"/>
......@@ -25,15 +25,15 @@
<small>{{$t('notifications.followed_you')}}</small>
</span>
</div>
<small class="timeago"><router-link @click.native="activatePanel('timeline')" v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
<small class="timeago"><router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
</span>
<div class="follow-text" v-if="notification.type === 'follow'">
<router-link @click.native="activatePanel('timeline')" :to="userProfileLink(notification.action.user)">
<router-link :to="userProfileLink(notification.action.user)">
@{{notification.action.user.screen_name}}
</router-link>
</div>
<template v-else>
<status :activatePanel="activatePanel" class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status>
<status class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status>
</template>
</div>
</div>
......
import Notification from '../notification/notification.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import { sortBy, filter } from 'lodash'
import {
notificationsFromStore,
visibleNotificationsFromStore,
unseenNotificationsFromStore
} from '../../services/notification_utils/notification_utils.js'
const Notifications = {
props: [ 'activatePanel' ],
created () {
const store = this.$store
const credentials = store.state.users.currentUser.credentials
......@@ -12,28 +14,17 @@ const Notifications = {
notificationsFetcher.startFetching({ store, credentials })
},
computed: {
visibleTypes () {
return [
this.$store.state.config.notificationVisibility.likes && 'like',
this.$store.state.config.notificationVisibility.mentions && 'mention',
this.$store.state.config.notificationVisibility.repeats && 'repeat',
this.$store.state.config.notificationVisibility.follows && 'follow'
].filter(_ => _)
},
notifications () {
return this.$store.state.statuses.notifications.data
return notificationsFromStore(this.$store)
},
error () {
return this.$store.state.statuses.notifications.error
},
unseenNotifications () {
return filter(this.visibleNotifications, ({seen}) => !seen)
return unseenNotificationsFromStore(this.$store)
},
visibleNotifications () {
// Don't know why, but sortBy([seen, -action.id]) doesn't work.
let sortedNotifications = sortBy(this.notifications, ({action}) => -action.id)
sortedNotifications = sortBy(sortedNotifications, 'seen')
return sortedNotifications.filter((notification) => this.visibleTypes.includes(notification.type))
return visibleNotificationsFromStore(this.$store)
},
unseenCount () {
return this.unseenNotifications.length
......
......@@ -14,7 +14,7 @@
<div class="panel-body">
<div v-for="notification in visibleNotifications" :key="notification.action.id" class="notification" :class='{"unseen": !notification.seen}'>
<div class="notification-overlay"></div>
<notification :activatePanel="activatePanel" :notification="notification"></notification>
<notification :notification="notification"></notification>
</div>
</div>
<div class="panel-footer">
......
import UserCardContent from '../user_card_content/user_card_content.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
const deltaX = (oldX, newX) => newX - oldX
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
const touchEventX = e => e.touches[0].screenX
const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
const SideDrawer = {
props: [ 'activatePanel', 'logout' ],
props: [ 'logout' ],
data: () => ({
closed: true,
touchX: 0
touchCoord: [0, 0]
}),
components: { UserCardContent },
computed: {
currentUser () {
return this.$store.state.users.currentUser
},
chat () { return this.$store.state.chat.channel.state === 'joined' },
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
unseenNotificationsCount () {
return this.unseenNotifications.length
}
},
methods: {
toggleDrawer () {
this.closed = !this.closed
},
gotoPanel (panel) {
this.activatePanel(panel)
this.toggleDrawer()
},
doLogout () {
this.logout()
this.gotoPanel('timeline')
this.toggleDrawer()
},
touchStart (e) {
this.touchX = touchEventX(e)
this.touchCoord = touchEventCoord(e)
},
touchMove (e) {
const delta = deltaX(this.touchX, touchEventX(e))
if (delta < -30 && !this.closed) {
const delta = deltaCoord(this.touchCoord, touchEventCoord(e))
if (delta[0] < -30 && Math.abs(delta[1]) < Math.abs(delta[0]) && !this.closed) {
this.toggleDrawer()
}
}
......
<template>
<div class="side-drawer-container" :class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }">
<div class="panel panel-default side-drawer"
<div class="side-drawer"
:class="{'side-drawer-closed': closed}"
@touchstart="touchStart"
@touchmove.prevent="touchMove"
@touchmove="touchMove"
>
<div class="side-drawer-heading">
<user-card-content :activatePanel="activatePanel" :user="currentUser" :switcher="false" :hideBio="true">
<div class="side-drawer-heading" @click="toggleDrawer">
<user-card-content :user="currentUser" :switcher="false" :hideBio="true">
</user-card-content>
</div>
<ul>
<li v-if='currentUser'>
<a href="#" @click="gotoPanel('poststatus')">
<li v-if="currentUser" @click="toggleDrawer">
<router-link :to="{ name: 'new-status', params: { username: currentUser.screen_name } }">
{{ $t("post_status.new_status") }}
</a>
</router-link>
</li>
<li v-else>
<a href="#" @click="gotoPanel('poststatus')">
<li v-else @click="toggleDrawer">
<router-link :to="{ name: 'login' }">
{{ $t("login.login") }}
</a>
</li>
<li v-if='currentUser'>
<a href="#" @click="gotoPanel('notifications')">
{{ $t("notifications.notifications") }}
</a>
</router-link>
</li>
<li v-if='currentUser'>
<router-link @click.native="gotoPanel('timeline')" to='/main/friends'>
{{ $t("nav.timeline") }}
<li v-if="currentUser" @click="toggleDrawer">
<router-link :to="{ name: 'notifications', params: { username: currentUser.screen_name } }">
{{ $t("notifications.notifications") }} {{ unseenNotificationsCount > 0 ? `(${unseenNotificationsCount})` : '' }}
</router-link>
</li>
<li v-if='currentUser'>
<router-link @click.native="gotoPanel('timeline')" :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
<li v-if="currentUser" @click="toggleDrawer">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }}
</router-link>
</li>
<li v-if='currentUser && currentUser.locked'>
<router-link @click.native="gotoPanel('timeline')" to='/friend-requests'>
<li>
<div class="divider"></div>
</li>
<li v-if="currentUser" @click="toggleDrawer">
<router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }}
</router-link>
</li>
<li v-if="currentUser && currentUser.locked" @click="toggleDrawer">
<router-link to='/friend-requests'>
{{ $t("nav.friend_requests") }}
</router-link>
</li>
<li>
<router-link @click.native="gotoPanel('timeline')" to='/main/public'>
<li @click="toggleDrawer">
<router-link to='/main/public'>
{{ $t("nav.public_tl") }}
</router-link>
</li>
<li>
<router-link @click.native="gotoPanel('timeline')" to='/main/all'>
<li @click="toggleDrawer">
<router-link to='/main/all'>
{{ $t("nav.twkn") }}
</router-link>
</li>
<li v-if="currentUser && chat" @click="toggleDrawer">
<router-link :to="{ name: 'chat' }">
{{ $t("nav.chat") }}
</router-link>
</li>
<li>
<router-link @click.native="gotoPanel('timeline')" :to="{ name: 'settings'}">
<div class="divider"></div>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'user-search'}">
{{ $t("nav.user_search") }}
</router-link>
</li>
<li @click="toggleDrawer">
<router-link :to="{ name: 'settings'}">
{{ $t("settings.settings") }}
</router-link>
</li>
<li v-if="currentUser">
<li v-if="currentUser" @click="toggleDrawer">
<a @click="doLogout" href="#">
{{ $t("login.logout") }}
</a>
......@@ -102,9 +118,14 @@
transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
margin: 0 0 0 -100px;
padding: 0 0 0 100px;
width: 75%;
flex: 0 0 75%;
padding: 0 0 1em 100px;
width: 80%;
max-width: 20em;
flex: 0 0 80%;
box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6);
box-shadow: var(--panelShadow);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
}
.side-drawer-click-outside-closed {
......@@ -115,18 +136,12 @@
margin: 0 0 0 calc(-100% - 100px);
}
.side-drawer .panel {
overflow: hidden;
margin: 0;
display: flex;
}
.side-drawer-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
display: flex;
min-height: 8em;
min-height: 7em;
padding: 0;
margin: 0;
......@@ -147,14 +162,18 @@
}
.side-drawer li {
border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
padding: 0;
.divider {
border-top: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
margin: 0.2em 0;
}
a {
display: block;
padding: 0.8em 0.85em;
padding: 0.5em 0.85em;
&:hover {
background-color: $fallback--lightBg;
......
......@@ -21,8 +21,7 @@ const Status = {
'replies',
'noReplyLinks',
'noHeading',
'inlineExpanded',
'activatePanel'
'inlineExpanded'
],
data () {
return {
......
......@@ -3,7 +3,7 @@
<template v-if="muted && !noReplyLinks">
<div class="media status container muted">
<small>
<router-link @click.native="activatePanel('timeline')" :to="userProfileLink(status.user.id, status.user.screen_name)">
<router-link :to="userProfileLink(status.user.id, status.user.screen_name)">
{{status.user.screen_name}}
</router-link>
</small>
......@@ -38,12 +38,12 @@
<h4 class="user-name" v-if="status.user.name_html" v-html="status.user.name_html"></h4>
<h4 class="user-name" v-else>{{status.user.name}}</h4>
<span class="links">
<router-link @click.native="activatePanel('timeline')" :to="userProfileLink(status.user.id, status.user.screen_name)">
<router-link :to="userProfileLink(status.user.id, status.user.screen_name)">
{{status.user.screen_name}}
</router-link>
<span v-if="status.in_reply_to_screen_name" class="faint reply-info">
<i class="icon-right-open"></i>
<router-link @click.native="activatePanel('timeline')" :to="userProfileLink(status.in_reply_to_user_id, status.in_reply_to_screen_name)">
<router-link :to="userProfileLink(status.in_reply_to_user_id, status.in_reply_to_screen_name)">
{{status.in_reply_to_screen_name}}
</router-link>
</span>
......@@ -60,7 +60,7 @@