logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
commit: c67e9daf068c5a7eafaa7ce6a6418c8916a4f118
parent: af3e69743e3192898f185fbc867defa1d155a4d4
Author: Shpuld Shpludson <shp@cock.li>
Date:   Fri,  1 May 2020 20:24:25 +0000

Merge branch 'follow-request-notification' into 'develop'

Add support for follow request notifications

Closes #823 and #822

See merge request pleroma/pleroma-fe!1093

Diffstat:

MCHANGELOG.md3+++
Msrc/components/notification/notification.js19+++++++++++++++++++
Msrc/components/notification/notification.vue44+++++++++++++++++++++++++++++++++-----------
Msrc/components/notifications/notifications.scss15+++++++++++++++
Msrc/i18n/en.json5++++-
Msrc/modules/config.js3++-
Msrc/modules/statuses.js22++++++++++++++++++++--
Msrc/services/api/api.service.js11+++++++++++
Msrc/services/entity_normalizer/entity_normalizer.service.js5++---
Msrc/services/notification_utils/notification_utils.js7++++++-
Mstatic/fontello.json15++++++++++++++-
11 files changed, 129 insertions(+), 20 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -11,6 +11,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - Emoji autocomplete will match any part of the word and not just start, for example :drool will now helpfully suggest :blobcatdrool: and :blobcatdroolreach: +### Add +- Follow request notification support + ## [2.0.2] - 2020-04-08 ### Fixed - Favorite/Repeat avatars not showing up on private instances/non-public posts diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js @@ -2,6 +2,7 @@ import Status from '../status/status.vue' import UserAvatar from '../user_avatar/user_avatar.vue' import UserCard from '../user_card/user_card.vue' import Timeago from '../timeago/timeago.vue' +import { isStatusNotification } from '../../services/notification_utils/notification_utils.js' import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -32,6 +33,21 @@ const Notification = { }, toggleMute () { this.unmuted = !this.unmuted + }, + approveUser () { + this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('updateNotification', { + id: this.notification.id, + updater: notification => { + notification.type = 'follow' + } + }) + }, + denyUser () { + this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) + this.$store.dispatch('removeFollowRequest', this.user) + this.$store.dispatch('dismissNotification', { id: this.notification.id }) } }, computed: { @@ -57,6 +73,9 @@ const Notification = { }, needMute () { return this.user.muted + }, + isStatusNotification () { + return isStatusNotification(this.notification.type) } } } diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue @@ -74,6 +74,10 @@ <i class="fa icon-user-plus lit" /> <small>{{ $t('notifications.followed_you') }}</small> </span> + <span v-if="notification.type === 'follow_request'"> + <i class="fa icon-user lit" /> + <small>{{ $t('notifications.follow_request') }}</small> + </span> <span v-if="notification.type === 'move'"> <i class="fa icon-arrow-curved lit" /> <small>{{ $t('notifications.migrated_to') }}</small> @@ -87,30 +91,30 @@ </span> </div> <div - v-if="notification.type === 'follow' || notification.type === 'move'" + v-if="isStatusNotification" class="timeago" > - <span class="faint"> + <router-link + v-if="notification.status" + :to="{ name: 'conversation', params: { id: notification.status.id } }" + class="faint-link" + > <Timeago :time="notification.created_at" :auto-update="240" /> - </span> + </router-link> </div> <div v-else class="timeago" > - <router-link - v-if="notification.status" - :to="{ name: 'conversation', params: { id: notification.status.id } }" - class="faint-link" - > + <span class="faint"> <Timeago :time="notification.created_at" :auto-update="240" /> - </router-link> + </span> </div> <a v-if="needMute" @@ -119,12 +123,30 @@ ><i class="button-icon icon-eye-off" /></a> </span> <div - v-if="notification.type === 'follow'" + v-if="notification.type === 'follow' || notification.type === 'follow_request'" class="follow-text" > - <router-link :to="userProfileLink"> + <router-link + :to="userProfileLink" + class="follow-name" + > @{{ notification.from_profile.screen_name }} </router-link> + <div + v-if="notification.type === 'follow_request'" + style="white-space: nowrap;" + > + <i + class="icon-ok button-icon add-reaction-button" + :title="$t('tool_tip.accept_follow_request')" + @click="approveUser()" + /> + <i + class="icon-cancel button-icon add-reaction-button" + :title="$t('tool_tip.accept_follow_request')" + @click="denyUser()" + /> + </div> </div> <div v-else-if="notification.type === 'move'" diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss @@ -82,6 +82,16 @@ .follow-text, .move-text { padding: 0.5em 0; overflow-wrap: break-word; + display: flex; + justify-content: space-between; + + .follow-name { + display: block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .status-el { @@ -143,6 +153,11 @@ color: var(--cGreen, $fallback--cGreen); } + .icon-user.lit { + color: $fallback--cBlue; + color: var(--cBlue, $fallback--cBlue); + } + .icon-user-plus.lit { color: $fallback--cBlue; color: var(--cBlue, $fallback--cBlue); diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -124,6 +124,7 @@ "broken_favorite": "Unknown status, searching for it...", "favorited_you": "favorited your status", "followed_you": "followed you", + "follow_request": "wants to follow you", "load_older": "Load older notifications", "notifications": "Notifications", "read": "Read!", @@ -697,7 +698,9 @@ "reply": "Reply", "favorite": "Favorite", "add_reaction": "Add Reaction", - "user_settings": "User Settings" + "user_settings": "User Settings", + "accept_follow_request": "Accept follow request", + "reject_follow_request": "Reject follow request" }, "upload":{ "error": { diff --git a/src/modules/config.js b/src/modules/config.js @@ -34,7 +34,8 @@ export const defaultState = { likes: true, repeats: true, moves: true, - emojiReactions: false + emojiReactions: false, + followRequest: true }, webPushNotifications: false, muteWords: [], diff --git a/src/modules/statuses.js b/src/modules/statuses.js @@ -13,6 +13,7 @@ import { omitBy } from 'lodash' import { set } from 'vue' +import { isStatusNotification } from '../services/notification_utils/notification_utils.js' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -321,7 +322,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => { each(notifications, (notification) => { - if (notification.type !== 'follow' && notification.type !== 'move') { + if (isStatusNotification(notification.type)) { notification.action = addStatusToGlobalStorage(state, notification.action).item notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item } @@ -361,13 +362,16 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot case 'move': i18nString = 'migrated_to' break + case 'follow_request': + i18nString = 'follow_request' + break } if (notification.type === 'pleroma:emoji_reaction') { notifObj.body = rootGetters.i18n.t('notifications.reacted_with', [notification.emoji]) } else if (i18nString) { notifObj.body = rootGetters.i18n.t('notifications.' + i18nString) - } else { + } else if (isStatusNotification(notification.type)) { notifObj.body = notification.status.text } @@ -521,6 +525,13 @@ export const mutations = { notification.seen = true }) }, + dismissNotification (state, { id }) { + state.notifications.data = state.notifications.data.filter(n => n.id !== id) + }, + updateNotification (state, { id, updater }) { + const notification = find(state.notifications.data, n => n.id === id) + notification && updater(notification) + }, queueFlush (state, { timeline, id }) { state.timelines[timeline].flushMarker = id }, @@ -680,6 +691,13 @@ const statuses = { credentials: rootState.users.currentUser.credentials }) }, + dismissNotification ({ rootState, commit }, { id }) { + rootState.api.backendInteractor.dismissNotification({ id }) + .then(() => commit('dismissNotification', { id })) + }, + updateNotification ({ rootState, commit }, { id, updater }) { + commit('updateNotification', { id, updater }) + }, fetchFavsAndRepeats ({ rootState, commit }, id) { Promise.all([ rootState.api.backendInteractor.fetchFavoritedByUsers({ id }), diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js @@ -29,6 +29,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications' +const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss` const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite` const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite` const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog` @@ -1006,6 +1007,15 @@ const unmuteDomain = ({ domain, credentials }) => { }) } +const dismissNotification = ({ credentials, id }) => { + return promisedRequest({ + url: MASTODON_DISMISS_NOTIFICATION_URL(id), + method: 'POST', + payload: { id }, + credentials + }) +} + export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { return Object.entries({ ...(credentials @@ -1161,6 +1171,7 @@ const apiService = { denyUser, suggestions, markNotificationsAsSeen, + dismissNotification, vote, fetchPoll, fetchFavoritedByUsers, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js @@ -1,4 +1,5 @@ import escape from 'escape-html' +import { isStatusNotification } from '../notification_utils/notification_utils.js' const qvitterStatusType = (status) => { if (status.is_post_verb) { @@ -346,9 +347,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type output.seen = data.pleroma.is_seen - output.status = output.type === 'follow' || output.type === 'move' - ? null - : parseStatus(data.status) + output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null output.action = output.status // TODO: Refactor, this is unneeded output.target = output.type !== 'move' ? null diff --git a/src/services/notification_utils/notification_utils.js b/src/services/notification_utils/notification_utils.js @@ -1,4 +1,4 @@ -import { filter, sortBy } from 'lodash' +import { filter, sortBy, includes } from 'lodash' export const notificationsFromStore = store => store.state.statuses.notifications.data @@ -7,10 +7,15 @@ export const visibleTypes = store => ([ store.state.config.notificationVisibility.mentions && 'mention', store.state.config.notificationVisibility.repeats && 'repeat', store.state.config.notificationVisibility.follows && 'follow', + store.state.config.notificationVisibility.followRequest && 'follow_request', store.state.config.notificationVisibility.moves && 'move', store.state.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction' ].filter(_ => _)) +const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction'] + +export const isStatusNotification = (type) => includes(statusNotifications, type) + const sortById = (a, b) => { const seqA = Number(a.id) const seqB = Number(b.id) diff --git a/static/fontello.json b/static/fontello.json @@ -345,6 +345,18 @@ "css": "link", "code": 59427, "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59428, + "src": "fontawesome" + }, + { + "uid": "12f4ece88e46abd864e40b35e05b11cd", + "css": "ok", + "code": 59431, + "src": "fontawesome" } ] -} +}+ \ No newline at end of file