commit: a954f56e3444e921dcf5ff6651e443ac110f1804
parent: 0f7f685c5e720d870cc732f07f68fb6eac278a68
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date: Tue, 30 Apr 2019 17:39:33 +0000
Merge branch 'brendenbice1222/pleroma-fe-issues/pleroma-fe-202-show-boosted-users' into 'develop'
Show favoriting and repeating users in hilighted status
See merge request pleroma/pleroma-fe!768
Diffstat:
11 files changed, 178 insertions(+), 5 deletions(-)
diff --git a/src/components/avatar_list/avatar_list.js b/src/components/avatar_list/avatar_list.js
@@ -0,0 +1,15 @@
+import UserAvatar from '../user_avatar/user_avatar.vue'
+
+const AvatarList = {
+ props: ['avatars'],
+ computed: {
+ slicedAvatars () {
+ return this.avatars ? this.avatars.slice(0, 15) : []
+ }
+ },
+ components: {
+ UserAvatar
+ }
+}
+
+export default AvatarList
diff --git a/src/components/avatar_list/avatar_list.vue b/src/components/avatar_list/avatar_list.vue
@@ -0,0 +1,38 @@
+<template>
+ <div class="avatars">
+ <div class="avatars-item" v-for="avatar in slicedAvatars">
+ <UserAvatar :src="avatar.profile_image_url" class="avatar-small" />
+ </div>
+ </div>
+</template>
+
+<script src="./avatar_list.js" ></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.avatars {
+ display: flex;
+ margin: 0;
+ padding: 0;
+
+ // For hiding overflowing elements
+ flex-wrap: wrap;
+ height: 24px;
+
+ .avatars-item {
+ margin: 0 0 5px 5px;
+
+ &:first-child {
+ padding-left: 5px;
+ }
+
+ .avatar-small {
+ border-radius: $fallback--avatarAltRadius;
+ border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ height: 24px;
+ width: 24px;
+ }
+ }
+}
+</style>
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
@@ -139,6 +139,7 @@ const conversation = {
},
setHighlight (id) {
this.highlight = id
+ this.$store.dispatch('fetchFavsAndRepeats', id)
},
getHighlight () {
return this.isExpanded ? this.highlight : null
diff --git a/src/components/status/status.js b/src/components/status/status.js
@@ -7,11 +7,12 @@ import UserCard from '../user_card/user_card.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import Gallery from '../gallery/gallery.vue'
import LinkPreview from '../link-preview/link-preview.vue'
+import AvatarList from '../avatar_list/avatar_list.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import fileType from 'src/services/file_type/file_type.service'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
-import { filter, find, unescape } from 'lodash'
+import { filter, find, unescape, uniqBy } from 'lodash'
const Status = {
name: 'Status',
@@ -97,6 +98,10 @@ const Status = {
return this.statusoid
}
},
+ statusFromGlobalRepository () {
+ // NOTE: Consider to replace status with statusFromGlobalRepository
+ return this.$store.state.statuses.allStatusesObject[this.status.id]
+ },
loggedIn () {
return !!this.$store.state.users.currentUser
},
@@ -257,6 +262,14 @@ const Status = {
return this.status.statusnet_html
}
return this.status.summary_html + '<br />' + this.status.statusnet_html
+ },
+ combinedFavsAndRepeatsAvatars () {
+ // Use the status from the global status repository since favs and repeats are saved in it
+ const combinedAvatars = [].concat(
+ this.statusFromGlobalRepository.favoritedBy,
+ this.statusFromGlobalRepository.rebloggedBy
+ )
+ return uniqBy(combinedAvatars, 'id')
}
},
components: {
@@ -268,7 +281,8 @@ const Status = {
UserCard,
UserAvatar,
Gallery,
- LinkPreview
+ LinkPreview,
+ AvatarList
},
methods: {
visibilityIcon (visibility) {
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
@@ -133,6 +133,24 @@
<link-preview :card="status.card" :size="attachmentSize" :nsfw="nsfwClickthrough" />
</div>
+ <transition name="fade">
+ <div class="favs-repeated-users" v-if="combinedFavsAndRepeatsAvatars.length > 0 && isFocused">
+ <div class="stats">
+ <div class="stat-count" v-if="statusFromGlobalRepository.rebloggedBy && statusFromGlobalRepository.rebloggedBy.length > 0">
+ <a class="stat-title">{{ $t('status.repeats') }}</a>
+ <div class="stat-number">{{ statusFromGlobalRepository.rebloggedBy.length }}</div>
+ </div>
+ <div class="stat-count" v-if="statusFromGlobalRepository.favoritedBy && statusFromGlobalRepository.favoritedBy.length > 0">
+ <a class="stat-title">{{ $t('status.favorites') }}</a>
+ <div class="stat-number">{{ statusFromGlobalRepository.favoritedBy.length }}</div>
+ </div>
+ <div class="avatar-row">
+ <AvatarList :avatars='combinedFavsAndRepeatsAvatars'></AvatarList>
+ </div>
+ </div>
+ </div>
+ </transition>
+
<div v-if="!noHeading && !isPreview" class='status-actions media-body'>
<div v-if="loggedIn">
<i class="button-icon icon-reply" v-on:click.prevent="toggleReplying" :title="$t('tool_tip.reply')" :class="{'icon-reply-active': replying}"></i>
@@ -612,6 +630,50 @@ a.unmute {
}
}
+.favs-repeated-users {
+ margin-top: $status-margin;
+
+ .stats {
+ width: 100%;
+ display: flex;
+ line-height: 1em;
+
+ .stat-count {
+ margin-right: $status-margin;
+
+ .stat-title {
+ color: var(--faint, $fallback--faint);
+ font-size: 12px;
+ text-transform: uppercase;
+ position: relative;
+ }
+
+ .stat-number {
+ font-weight: bolder;
+ font-size: 16px;
+ line-height: 1em;
+ }
+ }
+
+ .avatar-row {
+ flex: 1;
+ overflow: hidden;
+ position: relative;
+ display: flex;
+ align-items: center;
+
+ &::before {
+ content: '';
+ position: absolute;
+ height: 100%;
+ width: 1px;
+ left: 0;
+ background-color: var(--faint, $fallback--faint);
+ }
+ }
+ }
+}
+
@media all and (max-width: 800px) {
.status-el {
.retweet-info {
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
@@ -16,7 +16,7 @@
</div>
<div :class="classes.body">
<div class="timeline">
- <conversation
+ <conversation
v-for="status in timeline.visibleStatuses"
class="status-fadein"
:key="status.id"
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -394,6 +394,8 @@
"no_statuses": "No statuses"
},
"status": {
+ "favorites": "Favorites",
+ "repeats": "Repeats",
"reply_to": "Reply to",
"replies_list": "Replies:"
},
diff --git a/src/i18n/fi.json b/src/i18n/fi.json
@@ -222,6 +222,8 @@
"no_more_statuses": "Ei enempää viestejä"
},
"status": {
+ "favorites": "Tykkäykset",
+ "repeats": "Toistot",
"reply_to": "Vastaus",
"replies_list": "Vastaukset:"
},
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
@@ -459,6 +459,13 @@ export const mutations = {
},
queueFlush (state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
+ },
+ addFavsAndRepeats (state, { id, favoritedByUsers, rebloggedByUsers }) {
+ state.allStatusesObject[id] = {
+ ...state.allStatusesObject[id],
+ favoritedBy: favoritedByUsers,
+ rebloggedBy: rebloggedByUsers
+ }
}
}
@@ -524,6 +531,21 @@ const statuses = {
id: rootState.statuses.notifications.maxId,
credentials: rootState.users.currentUser.credentials
})
+ },
+ fetchFavsAndRepeats ({ rootState, commit }, id) {
+ Promise.all([
+ rootState.api.backendInteractor.fetchFavoritedByUsers(id),
+ rootState.api.backendInteractor.fetchRebloggedByUsers(id)
+ ]).then(([favoritedByUsers, rebloggedByUsers]) =>
+ commit(
+ 'addFavsAndRepeats',
+ {
+ id,
+ favoritedByUsers: favoritedByUsers.filter(_ => _),
+ rebloggedByUsers: rebloggedByUsers.filter(_ => _)
+ }
+ )
+ )
}
},
mutations
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
@@ -47,6 +47,8 @@ const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
+const MASTODON_STATUS_FAVORITEDBY_URL = id => `/api/v1/statuses/${id}/favourited_by`
+const MASTODON_STATUS_REBLOGGEDBY_URL = id => `/api/v1/statuses/${id}/reblogged_by`
const MASTODON_PROFILE_UPDATE_URL = '/api/v1/accounts/update_credentials'
import { each, map, concat, last } from 'lodash'
@@ -712,6 +714,14 @@ const markNotificationsAsSeen = ({id, credentials}) => {
}).then((data) => data.json())
}
+const fetchFavoritedByUsers = ({id}) => {
+ return promisedRequest(MASTODON_STATUS_FAVORITEDBY_URL(id)).then((users) => users.map(parseUser))
+}
+
+const fetchRebloggedByUsers = ({id}) => {
+ return promisedRequest(MASTODON_STATUS_REBLOGGEDBY_URL(id)).then((users) => users.map(parseUser))
+}
+
const apiService = {
verifyCredentials,
fetchTimeline,
@@ -761,7 +771,9 @@ const apiService = {
approveUser,
denyUser,
suggestions,
- markNotificationsAsSeen
+ markNotificationsAsSeen,
+ fetchFavoritedByUsers,
+ fetchRebloggedByUsers
}
export default apiService
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -113,6 +113,9 @@ const backendInteractorService = (credentials) => {
const deleteAccount = ({password}) => apiService.deleteAccount({credentials, password})
const changePassword = ({password, newPassword, newPasswordConfirmation}) => apiService.changePassword({credentials, password, newPassword, newPasswordConfirmation})
+ const fetchFavoritedByUsers = (id) => apiService.fetchFavoritedByUsers({id})
+ const fetchRebloggedByUsers = (id) => apiService.fetchRebloggedByUsers({id})
+
const backendInteractorServiceInstance = {
fetchStatus,
fetchConversation,
@@ -154,7 +157,9 @@ const backendInteractorService = (credentials) => {
changePassword,
fetchFollowRequests,
approveUser,
- denyUser
+ denyUser,
+ fetchFavoritedByUsers,
+ fetchRebloggedByUsers
}
return backendInteractorServiceInstance