Merge remote-tracking branch 'upstream/develop' into shigusegubu
* upstream/develop: (42 commits) filter outside of component fix i18n fix overflow behavior update styles and remove unnecessary stuff refactor showing favs and repeats logic display favs & reblogged users on expanded post in timeline view display avatars only on highlighted post add fadein effect to panel remove commented code display avatars list on highlighted post use transition wrapper component remove custom fadein logic use a tag for favs/repeated title use normal font weight instead of lighter use filter array function remove important in the property value remove important in css property use global fallback variable remove duplicates from avatars list add hover effect to label ...
This commit is contained in:
commit
de54368b87
11 changed files with 178 additions and 5 deletions
15
src/components/avatar_list/avatar_list.js
Normal file
15
src/components/avatar_list/avatar_list.js
Normal file
|
@ -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
|
38
src/components/avatar_list/avatar_list.vue
Normal file
38
src/components/avatar_list/avatar_list.vue
Normal file
|
@ -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>
|
|
@ -139,6 +139,7 @@ const conversation = {
|
|||
},
|
||||
setHighlight (id) {
|
||||
this.highlight = id
|
||||
this.$store.dispatch('fetchFavsAndRepeats', id)
|
||||
},
|
||||
getHighlight () {
|
||||
return this.isExpanded ? this.highlight : null
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -394,6 +394,8 @@
|
|||
"no_statuses": "No statuses"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "Favorites",
|
||||
"repeats": "Repeats",
|
||||
"reply_to": "Reply to",
|
||||
"replies_list": "Replies:"
|
||||
},
|
||||
|
|
|
@ -222,6 +222,8 @@
|
|||
"no_more_statuses": "Ei enempää viestejä"
|
||||
},
|
||||
"status": {
|
||||
"favorites": "Tykkäykset",
|
||||
"repeats": "Toistot",
|
||||
"reply_to": "Vastaus",
|
||||
"replies_list": "Vastaukset:"
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue