forked from AkkomaGang/akkoma-fe
Merge branch 'masto-remains' into 'develop'
Interactions 2.0, removing last bits of qvitter api. Only login/register and change background remains after that See merge request pleroma/pleroma-fe!792
This commit is contained in:
commit
b78ad8998d
15 changed files with 110 additions and 36 deletions
|
@ -3,7 +3,7 @@ import PublicAndExternalTimeline from 'components/public_and_external_timeline/p
|
||||||
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
|
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
|
||||||
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
|
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
|
||||||
import ConversationPage from 'components/conversation-page/conversation-page.vue'
|
import ConversationPage from 'components/conversation-page/conversation-page.vue'
|
||||||
import Mentions from 'components/mentions/mentions.vue'
|
import Interactions from 'components/interactions/interactions.vue'
|
||||||
import DMs from 'components/dm_timeline/dm_timeline.vue'
|
import DMs from 'components/dm_timeline/dm_timeline.vue'
|
||||||
import UserProfile from 'components/user_profile/user_profile.vue'
|
import UserProfile from 'components/user_profile/user_profile.vue'
|
||||||
import Settings from 'components/settings/settings.vue'
|
import Settings from 'components/settings/settings.vue'
|
||||||
|
@ -34,7 +34,7 @@ export default (store) => {
|
||||||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||||
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
||||||
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
|
{ name: 'external-user-profile', path: '/users/:id', component: UserProfile },
|
||||||
{ name: 'mentions', path: '/users/:username/mentions', component: Mentions },
|
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions },
|
||||||
{ name: 'dms', path: '/users/:username/dms', component: DMs },
|
{ name: 'dms', path: '/users/:username/dms', component: DMs },
|
||||||
{ name: 'settings', path: '/settings', component: Settings },
|
{ name: 'settings', path: '/settings', component: Settings },
|
||||||
{ name: 'registration', path: '/registration', component: Registration },
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
|
|
25
src/components/interactions/interactions.js
Normal file
25
src/components/interactions/interactions.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import Notifications from '../notifications/notifications.vue'
|
||||||
|
|
||||||
|
const tabModeDict = {
|
||||||
|
mentions: ['mention'],
|
||||||
|
'likes+repeats': ['repeat', 'like'],
|
||||||
|
follows: ['follow']
|
||||||
|
}
|
||||||
|
|
||||||
|
const Interactions = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
filterMode: tabModeDict['mentions']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onModeSwitch (index, dataset) {
|
||||||
|
this.filterMode = tabModeDict[dataset.filter]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Notifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Interactions
|
25
src/components/interactions/interactions.vue
Normal file
25
src/components/interactions/interactions.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<div class="title">
|
||||||
|
Interactions
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<tab-switcher
|
||||||
|
ref="tabSwitcher"
|
||||||
|
:onSwitch="onModeSwitch"
|
||||||
|
>
|
||||||
|
<span data-tab-dummy data-filter="mentions" :label="$t('nav.mentions')"/>
|
||||||
|
<span data-tab-dummy data-filter="likes+repeats" :label="$t('interactions.favs_repeats')"/>
|
||||||
|
<span data-tab-dummy data-filter="follows" :label="$t('interactions.follows')"/>
|
||||||
|
</tab-switcher>
|
||||||
|
<Notifications
|
||||||
|
ref="notifications"
|
||||||
|
:noHeading="true"
|
||||||
|
:minimalMode="true"
|
||||||
|
:filterMode="filterMode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./interactions.js"></script>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<Timeline :title="$t('nav.mentions')" v-bind:timeline="timeline" v-bind:timeline-name="'mentions'"/>
|
<Timeline :title="$t('nav.interactions')" v-bind:timeline="timeline" v-bind:timeline-name="'mentions'"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script src="./mentions.js"></script>
|
<script src="./mentions.js"></script>
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if='currentUser'>
|
<li v-if='currentUser'>
|
||||||
<router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
|
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||||
{{ $t("nav.mentions") }}
|
{{ $t("nav.interactions") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-if='currentUser'>
|
<li v-if='currentUser'>
|
||||||
|
|
|
@ -7,15 +7,24 @@ import {
|
||||||
} from '../../services/notification_utils/notification_utils.js'
|
} from '../../services/notification_utils/notification_utils.js'
|
||||||
|
|
||||||
const Notifications = {
|
const Notifications = {
|
||||||
props: [
|
props: {
|
||||||
'noHeading'
|
// 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
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
bottomedOut: false
|
bottomedOut: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
mainClass () {
|
||||||
|
return this.minimalMode ? '' : 'panel panel-default'
|
||||||
|
},
|
||||||
notifications () {
|
notifications () {
|
||||||
return notificationsFromStore(this.$store)
|
return notificationsFromStore(this.$store)
|
||||||
},
|
},
|
||||||
|
@ -26,7 +35,8 @@ const Notifications = {
|
||||||
return unseenNotificationsFromStore(this.$store)
|
return unseenNotificationsFromStore(this.$store)
|
||||||
},
|
},
|
||||||
visibleNotifications () {
|
visibleNotifications () {
|
||||||
return visibleNotificationsFromStore(this.$store)
|
console.log(this.filterMode)
|
||||||
|
return visibleNotificationsFromStore(this.$store, this.filterMode)
|
||||||
},
|
},
|
||||||
unseenCount () {
|
unseenCount () {
|
||||||
return this.unseenNotifications.length
|
return this.unseenNotifications.length
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
|
||||||
.notifications {
|
.notifications {
|
||||||
// a bit of a hack to allow scrolling below notifications
|
&:not(.minimal) {
|
||||||
padding-bottom: 15em;
|
// a bit of a hack to allow scrolling below notifications
|
||||||
|
padding-bottom: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
.loadmore-error {
|
.loadmore-error {
|
||||||
color: $fallback--text;
|
color: $fallback--text;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="notifications">
|
<div :class="{ minimal: minimalMode }" class="notifications">
|
||||||
<div class="panel panel-default">
|
<div :class="mainClass">
|
||||||
<div v-if="!noHeading" class="panel-heading">
|
<div v-if="!noHeading" class="panel-heading">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
{{$t('notifications.notifications')}}
|
{{$t('notifications.notifications')}}
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
|
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div v-for="notification in visibleNotifications" :key="notification.id" class="notification" :class='{"unseen": !notification.seen}'>
|
<div v-for="notification in visibleNotifications" :key="notification.id" class="notification" :class='{"unseen": !minimalMode && !notification.seen}'>
|
||||||
<div class="notification-overlay"></div>
|
<div class="notification-overlay"></div>
|
||||||
<notification :notification="notification"></notification>
|
<notification :notification="notification"></notification>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,9 @@
|
||||||
{{$t('notifications.no_more_notifications')}}
|
{{$t('notifications.no_more_notifications')}}
|
||||||
</div>
|
</div>
|
||||||
<a v-else-if="!loading" href="#" v-on:click.prevent="fetchOlderNotifications()">
|
<a v-else-if="!loading" href="#" v-on:click.prevent="fetchOlderNotifications()">
|
||||||
<div class="new-status-notification text-center panel-footer">{{$t('notifications.load_older')}}</div>
|
<div class="new-status-notification text-center panel-footer">
|
||||||
|
{{ minimalMode ? $t('interactions.load_older') : $t('notifications.load_older')}}
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div v-else class="new-status-notification text-center panel-footer">
|
<div v-else class="new-status-notification text-center panel-footer">
|
||||||
<i class="icon-spin3 animate-spin"/>
|
<i class="icon-spin3 animate-spin"/>
|
||||||
|
|
|
@ -26,6 +26,11 @@
|
||||||
{{ $t("nav.dms") }}
|
{{ $t("nav.dms") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="currentUser" @click="toggleDrawer">
|
||||||
|
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
|
||||||
|
{{ $t("nav.interactions") }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-if="currentUser" @click="toggleDrawer">
|
<li v-if="currentUser" @click="toggleDrawer">
|
||||||
|
|
|
@ -4,15 +4,18 @@ import './tab_switcher.scss'
|
||||||
|
|
||||||
export default Vue.component('tab-switcher', {
|
export default Vue.component('tab-switcher', {
|
||||||
name: 'TabSwitcher',
|
name: 'TabSwitcher',
|
||||||
props: ['renderOnlyFocused'],
|
props: ['renderOnlyFocused', 'onSwitch'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
active: this.$slots.default.findIndex(_ => _.tag)
|
active: this.$slots.default.findIndex(_ => _.tag)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
activateTab (index) {
|
activateTab (index, dataset) {
|
||||||
return () => {
|
return () => {
|
||||||
|
if (typeof this.onSwitch === 'function') {
|
||||||
|
this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset)
|
||||||
|
}
|
||||||
this.active = index
|
this.active = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +40,11 @@ export default Vue.component('tab-switcher', {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={ classesWrapper.join(' ')}>
|
<div class={ classesWrapper.join(' ')}>
|
||||||
<button disabled={slot.data.attrs.disabled} onClick={this.activateTab(index)} class={ classesTab.join(' ') }>{slot.data.attrs.label}</button>
|
<button
|
||||||
|
disabled={slot.data.attrs.disabled}
|
||||||
|
onClick={this.activateTab(index)}
|
||||||
|
class={classesTab.join(' ')}>
|
||||||
|
{slot.data.attrs.label}</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
"chat": "Local Chat",
|
"chat": "Local Chat",
|
||||||
"friend_requests": "Follow Requests",
|
"friend_requests": "Follow Requests",
|
||||||
"mentions": "Mentions",
|
"mentions": "Mentions",
|
||||||
|
"interactions": "Interactions",
|
||||||
"dms": "Direct Messages",
|
"dms": "Direct Messages",
|
||||||
"public_tl": "Public Timeline",
|
"public_tl": "Public Timeline",
|
||||||
"timeline": "Timeline",
|
"timeline": "Timeline",
|
||||||
|
@ -78,6 +79,11 @@
|
||||||
"repeated_you": "repeated your status",
|
"repeated_you": "repeated your status",
|
||||||
"no_more_notifications": "No more notifications"
|
"no_more_notifications": "No more notifications"
|
||||||
},
|
},
|
||||||
|
"interactions": {
|
||||||
|
"favs_repeats": "Repeats and Favorites",
|
||||||
|
"follows": "New follows",
|
||||||
|
"load_older": "Load older interactions"
|
||||||
|
},
|
||||||
"post_status": {
|
"post_status": {
|
||||||
"new_status": "Post new status",
|
"new_status": "Post new status",
|
||||||
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
|
"account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only posts.",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
"back": "Назад",
|
"back": "Назад",
|
||||||
"chat": "Локальный чат",
|
"chat": "Локальный чат",
|
||||||
"mentions": "Упоминания",
|
"mentions": "Упоминания",
|
||||||
|
"interactions": "Взаимодействия",
|
||||||
"public_tl": "Публичная лента",
|
"public_tl": "Публичная лента",
|
||||||
"timeline": "Лента",
|
"timeline": "Лента",
|
||||||
"twkn": "Федеративная лента"
|
"twkn": "Федеративная лента"
|
||||||
|
@ -36,6 +37,11 @@
|
||||||
"read": "Прочесть",
|
"read": "Прочесть",
|
||||||
"repeated_you": "повторил(а) ваш статус"
|
"repeated_you": "повторил(а) ваш статус"
|
||||||
},
|
},
|
||||||
|
"interactions": {
|
||||||
|
"favs_repeats": "Повторы и фавориты",
|
||||||
|
"follows": "Новые подписки",
|
||||||
|
"load_older": "Загрузить старые взаимодействия"
|
||||||
|
},
|
||||||
"post_status": {
|
"post_status": {
|
||||||
"account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
|
"account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков",
|
||||||
"account_not_locked_warning_link": "залочен",
|
"account_not_locked_warning_link": "залочен",
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
/* eslint-env browser */
|
/* eslint-env browser */
|
||||||
const LOGIN_URL = '/api/account/verify_credentials.json'
|
const LOGIN_URL = '/api/account/verify_credentials.json'
|
||||||
const ALL_FOLLOWING_URL = '/api/qvitter/allfollowing'
|
|
||||||
const MENTIONS_URL = '/api/statuses/mentions.json'
|
|
||||||
const REGISTRATION_URL = '/api/account/register.json'
|
const REGISTRATION_URL = '/api/account/register.json'
|
||||||
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
||||||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||||
|
@ -320,13 +318,6 @@ const fetchFollowers = ({id, maxId, sinceId, limit = 20, credentials}) => {
|
||||||
.then((data) => data.map(parseUser))
|
.then((data) => data.map(parseUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllFollowing = ({username, credentials}) => {
|
|
||||||
const url = `${ALL_FOLLOWING_URL}/${username}.json`
|
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
|
||||||
.then((data) => data.json())
|
|
||||||
.then((data) => data.map(parseUser))
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchFollowRequests = ({credentials}) => {
|
const fetchFollowRequests = ({credentials}) => {
|
||||||
const url = FOLLOW_REQUESTS_URL
|
const url = FOLLOW_REQUESTS_URL
|
||||||
return fetch(url, { headers: authHeaders(credentials) })
|
return fetch(url, { headers: authHeaders(credentials) })
|
||||||
|
@ -446,7 +437,6 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
||||||
const timelineUrls = {
|
const timelineUrls = {
|
||||||
public: MASTODON_PUBLIC_TIMELINE,
|
public: MASTODON_PUBLIC_TIMELINE,
|
||||||
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
||||||
mentions: MENTIONS_URL,
|
|
||||||
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
||||||
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
||||||
'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
|
'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
|
||||||
|
@ -747,7 +737,6 @@ const apiService = {
|
||||||
postStatus,
|
postStatus,
|
||||||
deleteStatus,
|
deleteStatus,
|
||||||
uploadMedia,
|
uploadMedia,
|
||||||
fetchAllFollowing,
|
|
||||||
fetchMutes,
|
fetchMutes,
|
||||||
muteUser,
|
muteUser,
|
||||||
unmuteUser,
|
unmuteUser,
|
||||||
|
|
|
@ -23,10 +23,6 @@ const backendInteractorService = (credentials) => {
|
||||||
return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials})
|
return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchAllFollowing = ({username}) => {
|
|
||||||
return apiService.fetchAllFollowing({username, credentials})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchUser = ({id}) => {
|
const fetchUser = ({id}) => {
|
||||||
return apiService.fetchUser({id, credentials})
|
return apiService.fetchUser({id, credentials})
|
||||||
}
|
}
|
||||||
|
@ -137,7 +133,6 @@ const backendInteractorService = (credentials) => {
|
||||||
unblockUser,
|
unblockUser,
|
||||||
fetchUser,
|
fetchUser,
|
||||||
fetchUserRelationship,
|
fetchUserRelationship,
|
||||||
fetchAllFollowing,
|
|
||||||
verifyCredentials: apiService.verifyCredentials,
|
verifyCredentials: apiService.verifyCredentials,
|
||||||
startFetchingTimeline,
|
startFetchingTimeline,
|
||||||
startFetchingNotifications,
|
startFetchingNotifications,
|
||||||
|
|
|
@ -25,11 +25,13 @@ const sortById = (a, b) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const visibleNotificationsFromStore = store => {
|
export const visibleNotificationsFromStore = (store, types) => {
|
||||||
// map is just to clone the array since sort mutates it and it causes some issues
|
// map is just to clone the array since sort mutates it and it causes some issues
|
||||||
let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
|
let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById)
|
||||||
sortedNotifications = sortBy(sortedNotifications, 'seen')
|
sortedNotifications = sortBy(sortedNotifications, 'seen')
|
||||||
return sortedNotifications.filter((notification) => visibleTypes(store).includes(notification.type))
|
return sortedNotifications.filter(
|
||||||
|
(notification) => (types || visibleTypes(store)).includes(notification.type)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unseenNotificationsFromStore = store =>
|
export const unseenNotificationsFromStore = store =>
|
||||||
|
|
Loading…
Reference in a new issue