diff --git a/src/boot/routes.js b/src/boot/routes.js index 7e54a98b..1a179099 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -3,7 +3,7 @@ import PublicAndExternalTimeline from 'components/public_and_external_timeline/p import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue' import TagTimeline from 'components/tag_timeline/tag_timeline.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 UserProfile from 'components/user_profile/user_profile.vue' import Settings from 'components/settings/settings.vue' @@ -34,7 +34,7 @@ export default (store) => { { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { 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: 'settings', path: '/settings', component: Settings }, { name: 'registration', path: '/registration', component: Registration }, diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js new file mode 100644 index 00000000..d4e3cc17 --- /dev/null +++ b/src/components/interactions/interactions.js @@ -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 diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue new file mode 100644 index 00000000..5a204ca7 --- /dev/null +++ b/src/components/interactions/interactions.vue @@ -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> diff --git a/src/components/mentions/mentions.vue b/src/components/mentions/mentions.vue index bba06da6..6b4e96e0 100644 --- a/src/components/mentions/mentions.vue +++ b/src/components/mentions/mentions.vue @@ -1,5 +1,5 @@ <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> <script src="./mentions.js"></script> diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue index 7a7212fb..e6e0f074 100644 --- a/src/components/nav_panel/nav_panel.vue +++ b/src/components/nav_panel/nav_panel.vue @@ -8,8 +8,8 @@ </router-link> </li> <li v-if='currentUser'> - <router-link :to="{ name: 'mentions', params: { username: currentUser.screen_name } }"> - {{ $t("nav.mentions") }} + <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> + {{ $t("nav.interactions") }} </router-link> </li> <li v-if='currentUser'> diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 5b13b98e..8c97eb04 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -7,15 +7,24 @@ import { } from '../../services/notification_utils/notification_utils.js' const Notifications = { - props: [ - 'noHeading' - ], + 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 + }, data () { return { bottomedOut: false } }, computed: { + mainClass () { + return this.minimalMode ? '' : 'panel panel-default' + }, notifications () { return notificationsFromStore(this.$store) }, @@ -26,7 +35,8 @@ const Notifications = { return unseenNotificationsFromStore(this.$store) }, visibleNotifications () { - return visibleNotificationsFromStore(this.$store) + console.log(this.filterMode) + return visibleNotificationsFromStore(this.$store, this.filterMode) }, unseenCount () { return this.unseenNotifications.length diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss index c0b458cc..622d12f4 100644 --- a/src/components/notifications/notifications.scss +++ b/src/components/notifications/notifications.scss @@ -1,8 +1,10 @@ @import '../../_variables.scss'; .notifications { - // a bit of a hack to allow scrolling below notifications - padding-bottom: 15em; + &:not(.minimal) { + // a bit of a hack to allow scrolling below notifications + padding-bottom: 15em; + } .loadmore-error { color: $fallback--text; diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index 88775be1..c71499b2 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -1,6 +1,6 @@ <template> - <div class="notifications"> - <div class="panel panel-default"> + <div :class="{ minimal: minimalMode }" class="notifications"> + <div :class="mainClass"> <div v-if="!noHeading" class="panel-heading"> <div class="title"> {{$t('notifications.notifications')}} @@ -12,7 +12,7 @@ <button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button> </div> <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> <notification :notification="notification"></notification> </div> @@ -22,7 +22,9 @@ {{$t('notifications.no_more_notifications')}} </div> <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> <div v-else class="new-status-notification text-center panel-footer"> <i class="icon-spin3 animate-spin"/> diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue index 9abb8cef..6428b1b0 100644 --- a/src/components/side_drawer/side_drawer.vue +++ b/src/components/side_drawer/side_drawer.vue @@ -26,6 +26,11 @@ {{ $t("nav.dms") }} </router-link> </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> <li v-if="currentUser" @click="toggleDrawer"> diff --git a/src/components/tab_switcher/tab_switcher.js b/src/components/tab_switcher/tab_switcher.js index 423df258..c949b458 100644 --- a/src/components/tab_switcher/tab_switcher.js +++ b/src/components/tab_switcher/tab_switcher.js @@ -4,15 +4,18 @@ import './tab_switcher.scss' export default Vue.component('tab-switcher', { name: 'TabSwitcher', - props: ['renderOnlyFocused'], + props: ['renderOnlyFocused', 'onSwitch'], data () { return { active: this.$slots.default.findIndex(_ => _.tag) } }, methods: { - activateTab (index) { + activateTab (index, dataset) { return () => { + if (typeof this.onSwitch === 'function') { + this.onSwitch.call(null, index, this.$slots.default[index].elm.dataset) + } this.active = index } } @@ -37,7 +40,11 @@ export default Vue.component('tab-switcher', { return ( <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> ) }) diff --git a/src/i18n/en.json b/src/i18n/en.json index dac0e38d..031c93de 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -60,6 +60,7 @@ "chat": "Local Chat", "friend_requests": "Follow Requests", "mentions": "Mentions", + "interactions": "Interactions", "dms": "Direct Messages", "public_tl": "Public Timeline", "timeline": "Timeline", @@ -78,6 +79,11 @@ "repeated_you": "repeated your status", "no_more_notifications": "No more notifications" }, + "interactions": { + "favs_repeats": "Repeats and Favorites", + "follows": "New follows", + "load_older": "Load older interactions" + }, "post_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.", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index b3ab322d..7a22194a 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -23,6 +23,7 @@ "back": "Назад", "chat": "Локальный чат", "mentions": "Упоминания", + "interactions": "Взаимодействия", "public_tl": "Публичная лента", "timeline": "Лента", "twkn": "Федеративная лента" @@ -36,6 +37,11 @@ "read": "Прочесть", "repeated_you": "повторил(а) ваш статус" }, + "interactions": { + "favs_repeats": "Повторы и фавориты", + "follows": "Новые подписки", + "load_older": "Загрузить старые взаимодействия" + }, "post_status": { "account_not_locked_warning": "Ваш аккаунт не {0}. Кто угодно может зафоловить вас чтобы прочитать посты только для подписчиков", "account_not_locked_warning_link": "залочен", diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js index 162b62f7..c67eccf1 100644 --- a/src/services/api/api.service.js +++ b/src/services/api/api.service.js @@ -1,7 +1,5 @@ /* eslint-env browser */ 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 BG_UPDATE_URL = '/api/qvitter/update_background_image.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)) } -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 url = FOLLOW_REQUESTS_URL return fetch(url, { headers: authHeaders(credentials) }) @@ -446,7 +437,6 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use const timelineUrls = { public: MASTODON_PUBLIC_TIMELINE, friends: MASTODON_USER_HOME_TIMELINE_URL, - mentions: MENTIONS_URL, dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL, notifications: MASTODON_USER_NOTIFICATIONS_URL, 'publicAndExternal': MASTODON_PUBLIC_TIMELINE, @@ -747,7 +737,6 @@ const apiService = { postStatus, deleteStatus, uploadMedia, - fetchAllFollowing, fetchMutes, muteUser, unmuteUser, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js index e23e1222..639bcabc 100644 --- a/src/services/backend_interactor_service/backend_interactor_service.js +++ b/src/services/backend_interactor_service/backend_interactor_service.js @@ -23,10 +23,6 @@ const backendInteractorService = (credentials) => { return apiService.fetchFollowers({id, maxId, sinceId, limit, credentials}) } - const fetchAllFollowing = ({username}) => { - return apiService.fetchAllFollowing({username, credentials}) - } - const fetchUser = ({id}) => { return apiService.fetchUser({id, credentials}) } @@ -137,7 +133,6 @@ const backendInteractorService = (credentials) => { unblockUser, fetchUser, fetchUserRelationship, - fetchAllFollowing, verifyCredentials: apiService.verifyCredentials, startFetchingTimeline, startFetchingNotifications, diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js index 8afd114e..f9cbbade 100644 --- a/src/services/notification_utils/notification_utils.js +++ b/src/services/notification_utils/notification_utils.js @@ -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 let sortedNotifications = notificationsFromStore(store).map(_ => _).sort(sortById) 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 =>