logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
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:

Asrc/components/avatar_list/avatar_list.js15+++++++++++++++
Asrc/components/avatar_list/avatar_list.vue38++++++++++++++++++++++++++++++++++++++
Msrc/components/conversation/conversation.js1+
Msrc/components/status/status.js18++++++++++++++++--
Msrc/components/status/status.vue62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/timeline/timeline.vue2+-
Msrc/i18n/en.json2++
Msrc/i18n/fi.json2++
Msrc/modules/statuses.js22++++++++++++++++++++++
Msrc/services/api/api.service.js14+++++++++++++-
Msrc/services/backend_interactor_service/backend_interactor_service.js7++++++-
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