Page MenuHomePhorge

No OneTemporary

Size
20 KB
Referenced Files
None
Subscribers
None
diff --git a/src/components/follow_list/follow_list.js b/src/components/follow_list/follow_list.js
index 6d00eb9457..acdb216d8e 100644
--- a/src/components/follow_list/follow_list.js
+++ b/src/components/follow_list/follow_list.js
@@ -1,62 +1,63 @@
import UserCard from '../user_card/user_card.vue'
const FollowList = {
data () {
return {
loading: false,
bottomedOut: false,
error: false
}
},
props: ['userId', 'showFollowers'],
created () {
window.addEventListener('scroll', this.scrollLoad)
if (this.entries.length === 0) {
this.fetchEntries()
}
},
destroyed () {
window.removeEventListener('scroll', this.scrollLoad)
this.$store.dispatch('clearFriendsAndFollowers', this.userId)
},
computed: {
user () {
return this.$store.getters.userById(this.userId)
},
entries () {
return this.showFollowers ? this.user.followers : this.user.friends
- }
+ },
+ showActions () { return this.$store.state.users.currentUser.id === this.userId }
},
methods: {
fetchEntries () {
if (!this.loading) {
const command = this.showFollowers ? 'addFollowers' : 'addFriends'
this.loading = true
this.$store.dispatch(command, this.userId).then(entries => {
this.error = false
this.loading = false
this.bottomedOut = entries.length === 0
}).catch(() => {
this.error = true
this.loading = false
})
}
},
scrollLoad (e) {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -(bodyBRect.y))
if (this.loading === false &&
this.bottomedOut === false &&
this.$el.offsetHeight > 0 &&
(window.innerHeight + window.pageYOffset) >= (height - 750)
) {
this.fetchEntries()
}
}
},
components: {
UserCard
}
}
export default FollowList
diff --git a/src/components/follow_list/follow_list.vue b/src/components/follow_list/follow_list.vue
index 24ab97d89a..7be2e7b77a 100644
--- a/src/components/follow_list/follow_list.vue
+++ b/src/components/follow_list/follow_list.vue
@@ -1,33 +1,34 @@
<template>
<div class="follow-list">
<user-card
v-for="entry in entries"
:key="entry.id" :user="entry"
- :showFollows="true"
+ :showFollows="!showFollowers"
+ :showActions="showActions"
/>
<div class="text-center panel-footer">
<a v-if="error" @click="fetchEntries" class="alert error">
{{$t('general.generic_error')}}
</a>
<i v-else-if="loading" class="icon-spin3 animate-spin"/>
<span v-else-if="bottomedOut"></span>
<a v-else @click="fetchEntries">{{$t('general.more')}}</a>
</div>
</div>
</template>
<script src="./follow_list.js"></script>
<style lang="scss">
.follow-list {
.panel-footer {
padding: 10px;
}
.error {
font-size: 14px;
}
}
</style>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index ecc36a4dd1..a4c84716d0 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -1,41 +1,65 @@
import UserCardContent from '../user_card_content/user_card_content.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
const UserCard = {
props: [
'user',
'showFollows',
- 'showApproval'
+ 'showApproval',
+ 'showActions'
],
data () {
return {
- userExpanded: false
+ userExpanded: false,
+ followRequestInProgress: false,
+ followRequestSent: false,
+ updated: false
}
},
components: {
UserCardContent,
UserAvatar
},
computed: {
- currentUser () { return this.$store.state.users.currentUser }
+ currentUser () { return this.$store.state.users.currentUser },
+ following () { return this.updated ? this.updated.following : this.user.following },
+ showFollow () {
+ return this.showActions && (!this.showFollows && !this.following || this.updated && !this.updated.following)
+ }
},
methods: {
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
approveUser () {
this.$store.state.api.backendInteractor.approveUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
},
denyUser () {
this.$store.state.api.backendInteractor.denyUser(this.user.id)
this.$store.dispatch('removeFollowRequest', this.user)
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
+ },
+ followUser () {
+ this.followRequestInProgress = true
+ requestFollow(this.user, this.$store).then(({ sent, updated }) => {
+ this.followRequestInProgress = false
+ this.followRequestSent = sent
+ this.updated = updated
+ })
+ },
+ unfollowUser () {
+ this.followRequestInProgress = true
+ requestUnfollow(this.user, this.$store).then(({ updated }) => {
+ this.followRequestInProgress = false
+ this.updated = updated
+ })
}
}
}
export default UserCard
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 7129430b85..12960c02d1 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -1,104 +1,137 @@
<template>
<div class="card">
<router-link :to="userProfileLink(user)">
<UserAvatar class="avatar" :compact="true" @click.prevent.native="toggleUserExpanded" :src="user.profile_image_url"/>
</router-link>
<div class="usercard" v-if="userExpanded">
<user-card-content :user="user" :switcher="false"></user-card-content>
</div>
<div class="name-and-screen-name" v-else>
- <div :title="user.name" v-if="user.name_html" class="user-name">
- <span v-html="user.name_html"></span>
+ <div :title="user.name" class="user-name">
+ <span v-if="user.name_html" v-html="user.name_html"></span>
+ <span v-else>{{ user.name }}</span>
<span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
{{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
</div>
- <div :title="user.name" v-else class="user-name">
- {{ user.name }}
- <span class="follows-you" v-if="!userExpanded && showFollows && user.follows_you">
- {{ currentUser.id == user.id ? $t('user_card.its_you') : $t('user_card.follows_you') }}
- </span>
+ <div class="user-link-action">
+ <router-link class='user-screen-name' :to="userProfileLink(user)">
+ @{{user.screen_name}}
+ </router-link>
+ <button
+ v-if="showFollow"
+ class="btn btn-default"
+ @click="followUser"
+ :disabled="followRequestInProgress"
+ :title="followRequestSent ? $t('user_card.follow_again') : ''"
+ >
+ <template v-if="followRequestInProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else-if="followRequestSent">
+ {{ $t('user_card.follow_sent') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow') }}
+ </template>
+ </button>
+ <button v-if="showActions && showFollows && following" class="btn btn-default" @click="unfollowUser" :disabled="followRequestInProgress">
+ <template v-if="followRequestInProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow_unfollow') }}
+ </template>
+ </button>
</div>
-
- <router-link class='user-screen-name' :to="userProfileLink(user)">
- @{{user.screen_name}}
- </router-link>
</div>
<div class="approval" v-if="showApproval">
<button class="btn btn-default" @click="approveUser">{{ $t('user_card.approve') }}</button>
<button class="btn btn-default" @click="denyUser">{{ $t('user_card.deny') }}</button>
</div>
</div>
</template>
<script src="./user_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.name-and-screen-name {
margin-left: 0.7em;
margin-top:0.0em;
text-align: left;
width: 100%;
.user-name {
+ display: flex;
+ justify-content: space-between;
+
img {
object-fit: contain;
height: 16px;
width: 16px;
vertical-align: middle;
}
}
+
+ .user-link-action {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+
+ button {
+ margin-top: 3px;
+ }
+ }
}
.follows-you {
margin-left: 2em;
- float: right;
}
.card {
display: flex;
flex: 1 0;
padding-top: 0.6em;
padding-right: 1em;
padding-bottom: 0.6em;
padding-left: 1em;
border-bottom: 1px solid;
margin: 0;
border-bottom-color: $fallback--border;
border-bottom-color: var(--border, $fallback--border);
.avatar {
padding: 0;
}
}
.usercard {
width: fill-available;
margin: 0.2em 0 0 0.7em;
border-radius: $fallback--panelRadius;
border-radius: var(--panelRadius, $fallback--panelRadius);
border-style: solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
border-width: 1px;
overflow: hidden;
.panel-heading {
background: transparent;
flex-direction: column;
align-items: stretch;
}
p {
margin-bottom: 0;
}
}
.approval {
button {
width: 100%;
margin-bottom: 0.5em;
}
}
</style>
diff --git a/src/components/user_card_content/user_card_content.js b/src/components/user_card_content/user_card_content.js
index 1888f8c6d2..7a7b89d490 100644
--- a/src/components/user_card_content/user_card_content.js
+++ b/src/components/user_card_content/user_card_content.js
@@ -1,192 +1,141 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
+import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
export default {
props: [ 'user', 'switcher', 'selected', 'hideBio' ],
data () {
return {
followRequestInProgress: false,
followRequestSent: false,
hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined'
? this.$store.state.instance.hideUserStats
: this.$store.state.config.hideUserStats,
betterShadow: this.$store.state.interface.browserSupport.cssFilter
}
},
computed: {
headingStyle () {
const color = this.$store.state.config.customTheme.colors
? this.$store.state.config.customTheme.colors.bg // v2
: this.$store.state.config.colors.bg // v1
if (color) {
const rgb = (typeof color === 'string') ? hex2rgb(color) : color
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)`
const gradient = [
[tintColor, this.hideBio ? '60%' : ''],
this.hideBio ? [
color, '100%'
] : [
tintColor, ''
]
].map(_ => _.join(' ')).join(', ')
return {
backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,
backgroundImage: [
`linear-gradient(to bottom, ${gradient})`,
`url(${this.user.cover_photo})`
].join(', ')
}
}
},
isOtherUser () {
return this.user.id !== this.$store.state.users.currentUser.id
},
subscribeUrl () {
// eslint-disable-next-line no-undef
const serverUrl = new URL(this.user.statusnet_profile_url)
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
},
loggedIn () {
return this.$store.state.users.currentUser
},
dailyAvg () {
const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000))
return Math.round(this.user.statuses_count / days)
},
userHighlightType: {
get () {
const data = this.$store.state.config.highlight[this.user.screen_name]
return data && data.type || 'disabled'
},
set (type) {
const data = this.$store.state.config.highlight[this.user.screen_name]
if (type !== 'disabled') {
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: data && data.color || '#FFFFFF', type })
} else {
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color: undefined })
}
}
},
userHighlightColor: {
get () {
const data = this.$store.state.config.highlight[this.user.screen_name]
return data && data.color
},
set (color) {
this.$store.dispatch('setHighlight', { user: this.user.screen_name, color })
}
},
visibleRole () {
const validRole = (this.user.role === 'admin' || this.user.role === 'moderator')
const showRole = this.isOtherUser || this.user.show_role
return validRole && showRole && this.user.role
}
},
components: {
UserAvatar
},
methods: {
followUser () {
- const store = this.$store
this.followRequestInProgress = true
- store.state.api.backendInteractor.followUser(this.user.id)
- .then((followedUser) => store.commit('addNewUsers', [followedUser]))
- .then(() => {
- // For locked users we just mark it that we sent the follow request
- if (this.user.locked) {
- this.followRequestInProgress = false
- this.followRequestSent = true
- return
- }
-
- if (this.user.following) {
- // If we get result immediately, just stop.
- this.followRequestInProgress = false
- return
- }
-
- // But usually we don't get result immediately, so we ask server
- // for updated user profile to confirm if we are following them
- // Sometimes it takes several tries. Sometimes we end up not following
- // user anyway, probably because they locked themselves and we
- // don't know that yet.
- // Recursive Promise, it will call itself up to 3 times.
- const fetchUser = (attempt) => new Promise((resolve, reject) => {
- setTimeout(() => {
- store.state.api.backendInteractor.fetchUser({ id: this.user.id })
- .then((user) => store.commit('addNewUsers', [user]))
- .then(() => resolve([this.user.following, attempt]))
- .catch((e) => reject(e))
- }, 500)
- }).then(([following, attempt]) => {
- if (!following && attempt <= 3) {
- // If we BE reports that we still not following that user - retry,
- // increment attempts by one
- return fetchUser(++attempt)
- } else {
- // If we run out of attempts, just return whatever status is.
- return following
- }
- })
-
- return fetchUser(1)
- .then((following) => {
- if (following) {
- // We confirmed and everything its good.
- this.followRequestInProgress = false
- } else {
- // If after all the tries, just treat it as if user is locked
- this.followRequestInProgress = false
- this.followRequestSent = true
- }
- })
- })
+ requestFollow(this.user, this.$store).then(({sent}) => {
+ this.followRequestInProgress = false
+ this.followRequestSent = sent
+ })
},
unfollowUser () {
- const store = this.$store
this.followRequestInProgress = true
- store.state.api.backendInteractor.unfollowUser(this.user.id)
- .then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
- .then(() => {
- this.followRequestInProgress = false
- })
+ requestUnfollow(this.user, this.$store).then(() => {
+ this.followRequestInProgress = false
+ })
},
blockUser () {
const store = this.$store
store.state.api.backendInteractor.blockUser(this.user.id)
.then((blockedUser) => store.commit('addNewUsers', [blockedUser]))
},
unblockUser () {
const store = this.$store
store.state.api.backendInteractor.unblockUser(this.user.id)
.then((unblockedUser) => store.commit('addNewUsers', [unblockedUser]))
},
toggleMute () {
const store = this.$store
store.commit('setMuted', {user: this.user, muted: !this.user.muted})
store.state.api.backendInteractor.setUserMute(this.user)
},
setProfileView (v) {
if (this.switcher) {
const store = this.$store
store.commit('setProfileView', { v })
}
},
linkClicked ({target}) {
if (target.tagName === 'SPAN') {
target = target.parentNode
}
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
},
userProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
}
}
}
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
new file mode 100644
index 0000000000..1e9bd67916
--- /dev/null
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -0,0 +1,74 @@
+const fetchUser = (attempt, user, store) => new Promise((resolve, reject) => {
+ setTimeout(() => {
+ store.state.api.backendInteractor.fetchUser({ id: user.id })
+ .then((user) => store.commit('addNewUsers', [user]))
+ .then(() => resolve([user.following, attempt]))
+ .catch((e) => reject(e))
+ }, 500)
+}).then(([following, attempt]) => {
+ if (!following && attempt <= 3) {
+ // If we BE reports that we still not following that user - retry,
+ // increment attempts by one
+ return fetchUser(++attempt, user, store)
+ } else {
+ // If we run out of attempts, just return whatever status is.
+ return following
+ }
+})
+
+export const requestFollow = (user, store) => new Promise((resolve, reject) => {
+ store.state.api.backendInteractor.followUser(user.id)
+ .then((updated) => {
+ store.commit('addNewUsers', [updated])
+
+ // For locked users we just mark it that we sent the follow request
+ if (updated.locked) {
+ resolve({
+ sent: true,
+ updated
+ })
+ }
+
+ if (updated.following) {
+ // If we get result immediately, just stop.
+ resolve({
+ sent: false,
+ updated
+ })
+ }
+
+ // But usually we don't get result immediately, so we ask server
+ // for updated user profile to confirm if we are following them
+ // Sometimes it takes several tries. Sometimes we end up not following
+ // user anyway, probably because they locked themselves and we
+ // don't know that yet.
+ // Recursive Promise, it will call itself up to 3 times.
+
+ return fetchUser(1, user, store)
+ .then((following) => {
+ if (following) {
+ // We confirmed and everything's good.
+ resolve({
+ sent: false,
+ updated
+ })
+ } else {
+ // If after all the tries, just treat it as if user is locked
+ resolve({
+ sent: false,
+ updated
+ })
+ }
+ })
+ })
+})
+
+export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
+ store.state.api.backendInteractor.unfollowUser(user.id)
+ .then((updated) => {
+ store.commit('addNewUsers', [updated])
+ resolve({
+ updated
+ })
+ })
+})

File Metadata

Mime Type
text/x-diff
Expires
Sun, Aug 9, 4:27 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724201
Default Alt Text
(20 KB)

Event Timeline