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:
HJ 2019-05-20 20:54:05 +00:00
commit b78ad8998d
15 changed files with 110 additions and 36 deletions

View file

@ -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 },

View 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

View 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>

View file

@ -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>

View file

@ -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'>

View file

@ -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

View file

@ -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;

View file

@ -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"/>

View file

@ -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">

View file

@ -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>
) )
}) })

View file

@ -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.",

View file

@ -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": "залочен",

View file

@ -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,

View file

@ -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,

View file

@ -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 =>