Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85833045
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
24 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js
index ac4e265a78..dc4a0d411b 100644
--- a/src/components/follow_card/follow_card.js
+++ b/src/components/follow_card/follow_card.js
@@ -1,50 +1,45 @@
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
const FollowCard = {
props: [
'user',
'noFollowsYou'
],
data () {
return {
inProgress: false,
- requestSent: false,
- updated: false
+ requestSent: false
}
},
components: {
BasicUserCard,
RemoteFollow
},
computed: {
- isMe () { return this.$store.state.users.currentUser.id === this.user.id },
- following () { return this.updated ? this.updated.following : this.user.following },
- showFollow () {
- return !this.following || this.updated && !this.updated.following
+ isMe () {
+ return this.$store.state.users.currentUser.id === this.user.id
},
loggedIn () {
return this.$store.state.users.currentUser
}
},
methods: {
followUser () {
this.inProgress = true
- requestFollow(this.user, this.$store).then(({ sent, updated }) => {
+ requestFollow(this.user, this.$store).then(({ sent }) => {
this.inProgress = false
this.requestSent = sent
- this.updated = updated
})
},
unfollowUser () {
this.inProgress = true
- requestUnfollow(this.user, this.$store).then(({ updated }) => {
+ requestUnfollow(this.user, this.$store).then(() => {
this.inProgress = false
- this.updated = updated
})
}
}
}
export default FollowCard
diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue
index 9f314fd302..94e2836fcc 100644
--- a/src/components/follow_card/follow_card.vue
+++ b/src/components/follow_card/follow_card.vue
@@ -1,58 +1,62 @@
<template>
<basic-user-card :user="user">
<div class="follow-card-content-container">
<span class="faint" v-if="!noFollowsYou && user.follows_you">
{{ isMe ? $t('user_card.its_you') : $t('user_card.follows_you') }}
</span>
- <div class="follow-card-follow-button" v-if="showFollow && !loggedIn">
- <RemoteFollow :user="user" />
- </div>
- <button
- v-if="showFollow && loggedIn"
- class="btn btn-default follow-card-follow-button"
- @click="followUser"
- :disabled="inProgress"
- :title="requestSent ? $t('user_card.follow_again') : ''"
- >
- <template v-if="inProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else-if="requestSent">
- {{ $t('user_card.follow_sent') }}
- </template>
- <template v-else>
- {{ $t('user_card.follow') }}
- </template>
- </button>
- <button v-if="following" class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress">
- <template v-if="inProgress">
- {{ $t('user_card.follow_progress') }}
- </template>
- <template v-else>
- {{ $t('user_card.follow_unfollow') }}
- </template>
- </button>
+ <template v-if="!loggedIn">
+ <div class="follow-card-follow-button" v-if="!user.following">
+ <RemoteFollow :user="user" />
+ </div>
+ </template>
+ <template v-else>
+ <button
+ v-if="!user.following"
+ class="btn btn-default follow-card-follow-button"
+ @click="followUser"
+ :disabled="inProgress"
+ :title="requestSent ? $t('user_card.follow_again') : ''"
+ >
+ <template v-if="inProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else-if="requestSent">
+ {{ $t('user_card.follow_sent') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow') }}
+ </template>
+ </button>
+ <button v-else class="btn btn-default follow-card-follow-button pressed" @click="unfollowUser" :disabled="inProgress">
+ <template v-if="inProgress">
+ {{ $t('user_card.follow_progress') }}
+ </template>
+ <template v-else>
+ {{ $t('user_card.follow_unfollow') }}
+ </template>
+ </button>
+ </template>
</div>
</basic-user-card>
</template>
<script src="./follow_card.js"></script>
<style lang="scss">
.follow-card {
&-content-container {
flex-shrink: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
line-height: 1.5em;
}
&-follow-button {
margin-top: 0.5em;
margin-left: auto;
width: 10em;
}
}
</style>
diff --git a/src/components/user_search/user_search.js b/src/components/user_search/user_search.js
index 550408260a..62dafdf134 100644
--- a/src/components/user_search/user_search.js
+++ b/src/components/user_search/user_search.js
@@ -1,45 +1,51 @@
import FollowCard from '../follow_card/follow_card.vue'
-import userSearchApi from '../../services/new_api/user_search.js'
+import map from 'lodash/map'
+
const userSearch = {
components: {
FollowCard
},
props: [
'query'
],
data () {
return {
username: '',
- users: [],
+ userIds: [],
loading: false
}
},
+ computed: {
+ users () {
+ return this.userIds.map(userId => this.$store.getters.findUser(userId))
+ }
+ },
mounted () {
this.search(this.query)
},
watch: {
query (newV) {
this.search(newV)
}
},
methods: {
newQuery (query) {
this.$router.push({ name: 'user-search', query: { query } })
this.$refs.userSearchInput.focus()
},
search (query) {
if (!query) {
this.users = []
return
}
this.loading = true
- userSearchApi.search({query, store: this.$store})
+ this.$store.dispatch('searchUsers', query)
.then((res) => {
this.loading = false
- this.users = res
+ this.userIds = map(res, 'id')
})
}
}
}
export default userSearch
diff --git a/src/modules/users.js b/src/modules/users.js
index c98e353a03..adcab2334a 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,455 +1,464 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
+import userSearchApi from '../services/new_api/user_search.js'
import { compact, map, each, merge, last, concat, uniq } from 'lodash'
import { set } from 'vue'
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
import oauthApi from '../services/new_api/oauth'
import { humanizeErrors } from './errors'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
if (!item) { return false }
const oldItem = obj[item.id]
if (oldItem) {
// We already have this, so only merge the new info.
merge(oldItem, item)
return { item: oldItem, new: false }
} else {
// This is a new item, prepare it
arr.push(item)
set(obj, item.id, item)
if (item.screen_name && !item.screen_name.includes('@')) {
set(obj, item.screen_name.toLowerCase(), item)
}
return { item, new: true }
}
}
const getNotificationPermission = () => {
const Notification = window.Notification
if (!Notification) return Promise.resolve(null)
if (Notification.permission === 'default') return Notification.requestPermission()
return Promise.resolve(Notification.permission)
}
const blockUser = (store, id) => {
return store.rootState.api.backendInteractor.blockUser(id)
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addBlockId', id)
store.commit('removeStatus', { timeline: 'friends', userId: id })
store.commit('removeStatus', { timeline: 'public', userId: id })
store.commit('removeStatus', { timeline: 'publicAndExternal', userId: id })
})
}
const unblockUser = (store, id) => {
return store.rootState.api.backendInteractor.unblockUser(id)
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
}
const muteUser = (store, id) => {
return store.rootState.api.backendInteractor.muteUser(id)
.then((relationship) => {
store.commit('updateUserRelationship', [relationship])
store.commit('addMuteId', id)
})
}
const unmuteUser = (store, id) => {
return store.rootState.api.backendInteractor.unmuteUser(id)
.then((relationship) => store.commit('updateUserRelationship', [relationship]))
}
export const mutations = {
setMuted (state, { user: { id }, muted }) {
const user = state.usersObject[id]
set(user, 'muted', muted)
},
tagUser (state, { user: { id }, tag }) {
const user = state.usersObject[id]
const tags = user.tags || []
const newTags = tags.concat([tag])
set(user, 'tags', newTags)
},
untagUser (state, { user: { id }, tag }) {
const user = state.usersObject[id]
const tags = user.tags || []
const newTags = tags.filter(t => t !== tag)
set(user, 'tags', newTags)
},
updateRight (state, { user: { id }, right, value }) {
const user = state.usersObject[id]
let newRights = user.rights
newRights[right] = value
set(user, 'rights', newRights)
},
updateActivationStatus (state, { user: { id }, status }) {
const user = state.usersObject[id]
set(user, 'deactivated', !status)
},
setCurrentUser (state, user) {
state.lastLoginName = user.screen_name
state.currentUser = merge(state.currentUser || {}, user)
},
clearCurrentUser (state) {
state.currentUser = false
state.lastLoginName = false
},
beginLogin (state) {
state.loggingIn = true
},
endLogin (state) {
state.loggingIn = false
},
saveFriendIds (state, { id, friendIds }) {
const user = state.usersObject[id]
user.friendIds = uniq(concat(user.friendIds, friendIds))
},
saveFollowerIds (state, { id, followerIds }) {
const user = state.usersObject[id]
user.followerIds = uniq(concat(user.followerIds, followerIds))
},
// Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile.
clearFriends (state, userId) {
const user = state.usersObject[userId]
if (user) {
set(user, 'friendIds', [])
}
},
clearFollowers (state, userId) {
const user = state.usersObject[userId]
if (user) {
set(user, 'followerIds', [])
}
},
addNewUsers (state, users) {
each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
updateUserRelationship (state, relationships) {
relationships.forEach((relationship) => {
const user = state.usersObject[relationship.id]
if (user) {
user.follows_you = relationship.followed_by
user.following = relationship.following
user.muted = relationship.muting
user.statusnet_blocking = relationship.blocking
}
})
},
updateBlocks (state, blockedUsers) {
// Reset statusnet_blocking of all fetched users
each(state.users, (user) => { user.statusnet_blocking = false })
each(blockedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds
},
addBlockId (state, blockId) {
if (state.currentUser.blockIds.indexOf(blockId) === -1) {
state.currentUser.blockIds.push(blockId)
}
},
updateMutes (state, mutedUsers) {
// Reset muted of all fetched users
each(state.users, (user) => { user.muted = false })
each(mutedUsers, (user) => mergeOrAdd(state.users, state.usersObject, user))
},
saveMuteIds (state, muteIds) {
state.currentUser.muteIds = muteIds
},
addMuteId (state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) {
state.currentUser.muteIds.push(muteId)
}
},
setUserForStatus (state, status) {
status.user = state.usersObject[status.user.id]
},
setUserForNotification (state, notification) {
if (notification.type !== 'follow') {
notification.action.user = state.usersObject[notification.action.user.id]
}
notification.from_profile = state.usersObject[notification.from_profile.id]
},
setColor (state, { user: { id }, highlighted }) {
const user = state.usersObject[id]
set(user, 'highlight', highlighted)
},
signUpPending (state) {
state.signUpPending = true
state.signUpErrors = []
},
signUpSuccess (state) {
state.signUpPending = false
},
signUpFailure (state, errors) {
state.signUpPending = false
state.signUpErrors = errors
}
}
export const getters = {
findUser: state => query => {
const result = state.usersObject[query]
// In case it's a screen_name, we can try searching case-insensitive
if (!result && typeof query === 'string') {
return state.usersObject[query.toLowerCase()]
}
return result
}
}
export const defaultState = {
loggingIn: false,
lastLoginName: false,
currentUser: false,
users: [],
usersObject: {},
signUpPending: false,
signUpErrors: []
}
const users = {
state: defaultState,
mutations,
getters,
actions: {
fetchUser (store, id) {
return store.rootState.api.backendInteractor.fetchUser({ id })
.then((user) => {
store.commit('addNewUsers', [user])
return user
})
},
fetchUserRelationship (store, id) {
if (store.state.currentUser) {
store.rootState.api.backendInteractor.fetchUserRelationship({ id })
.then((relationships) => store.commit('updateUserRelationship', relationships))
}
},
fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks()
.then((blocks) => {
store.commit('saveBlockIds', map(blocks, 'id'))
store.commit('updateBlocks', blocks)
return blocks
})
},
blockUser (store, id) {
return blockUser(store, id)
},
unblockUser (store, id) {
return unblockUser(store, id)
},
blockUsers (store, ids = []) {
return Promise.all(ids.map(id => blockUser(store, id)))
},
unblockUsers (store, ids = []) {
return Promise.all(ids.map(id => unblockUser(store, id)))
},
fetchMutes (store) {
return store.rootState.api.backendInteractor.fetchMutes()
.then((mutes) => {
store.commit('updateMutes', mutes)
store.commit('saveMuteIds', map(mutes, 'id'))
return mutes
})
},
muteUser (store, id) {
return muteUser(store, id)
},
unmuteUser (store, id) {
return unmuteUser(store, id)
},
muteUsers (store, ids = []) {
return Promise.all(ids.map(id => muteUser(store, id)))
},
unmuteUsers (store, ids = []) {
return Promise.all(ids.map(id => unmuteUser(store, id)))
},
fetchFriends ({ rootState, commit }, id) {
const user = rootState.users.usersObject[id]
const maxId = last(user.friendIds)
return rootState.api.backendInteractor.fetchFriends({ id, maxId })
.then((friends) => {
commit('addNewUsers', friends)
commit('saveFriendIds', { id, friendIds: map(friends, 'id') })
return friends
})
},
fetchFollowers ({ rootState, commit }, id) {
const user = rootState.users.usersObject[id]
const maxId = last(user.followerIds)
return rootState.api.backendInteractor.fetchFollowers({ id, maxId })
.then((followers) => {
commit('addNewUsers', followers)
commit('saveFollowerIds', { id, followerIds: map(followers, 'id') })
return followers
})
},
clearFriends ({ commit }, userId) {
commit('clearFriends', userId)
},
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
},
registerPushNotifications (store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey
const isEnabled = store.rootState.config.webPushNotifications
const notificationVisibility = store.rootState.config.notificationVisibility
registerPushNotifications(isEnabled, vapidPublicKey, token, notificationVisibility)
},
unregisterPushNotifications (store) {
const token = store.state.currentUser.credentials
unregisterPushNotifications(token)
},
addNewUsers ({ commit }, users) {
commit('addNewUsers', users)
},
addNewStatuses (store, { statuses }) {
const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
store.commit('addNewUsers', users)
store.commit('addNewUsers', retweetedUsers)
// Reconnect users to statuses
each(statuses, (status) => {
store.commit('setUserForStatus', status)
})
// Reconnect users to retweets
each(compact(map(statuses, 'retweeted_status')), (status) => {
store.commit('setUserForStatus', status)
})
},
addNewNotifications (store, { notifications }) {
const users = map(notifications, 'from_profile')
const notificationIds = notifications.map(_ => _.id)
store.commit('addNewUsers', users)
const notificationsObject = store.rootState.statuses.notifications.idStore
const relevantNotifications = Object.entries(notificationsObject)
.filter(([k, val]) => notificationIds.includes(k))
.map(([k, val]) => val)
// Reconnect users to notifications
each(relevantNotifications, (notification) => {
store.commit('setUserForNotification', notification)
})
},
+ searchUsers (store, query) {
+ // TODO: Move userSearch api into api.service
+ return userSearchApi.search({query, store: { state: store.rootState }})
+ .then((users) => {
+ store.commit('addNewUsers', users)
+ return users
+ })
+ },
async signUp (store, userInfo) {
store.commit('signUpPending')
let rootState = store.rootState
let response = await rootState.api.backendInteractor.register(userInfo)
if (response.ok) {
const data = {
oauth: rootState.oauth,
instance: rootState.instance.server
}
let app = await oauthApi.getOrCreateApp(data)
let result = await oauthApi.getTokenWithCredentials({
app,
instance: data.instance,
username: userInfo.username,
password: userInfo.password
})
store.commit('signUpSuccess')
store.commit('setToken', result.access_token)
store.dispatch('loginUser', result.access_token)
} else {
const data = await response.json()
let errors = JSON.parse(data.error)
// replace ap_id with username
if (errors.ap_id) {
errors.username = errors.ap_id
delete errors.ap_id
}
errors = humanizeErrors(errors)
store.commit('signUpFailure', errors)
throw Error(errors)
}
},
async getCaptcha (store) {
return await store.rootState.api.backendInteractor.getCaptcha()
},
logout (store) {
store.commit('clearCurrentUser')
store.dispatch('disconnectFromChat')
store.commit('setToken', false)
store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService())
store.dispatch('stopFetching', 'notifications')
store.commit('clearNotifications')
store.commit('resetStatuses')
},
loginUser (store, accessToken) {
return new Promise((resolve, reject) => {
const commit = store.commit
commit('beginLogin')
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
.then((data) => {
if (!data.error) {
const user = data
// user.credentials = userCredentials
user.credentials = accessToken
user.blockIds = []
user.muteIds = []
commit('setCurrentUser', user)
commit('addNewUsers', [user])
getNotificationPermission()
.then(permission => commit('setNotificationPermission', permission))
// Set our new backend interactor
commit('setBackendInteractor', backendInteractorService(accessToken))
if (user.token) {
store.dispatch('setWsToken', user.token)
// Initialize the chat socket.
store.dispatch('initializeSocket')
}
// Start getting fresh posts.
store.dispatch('startFetchingTimeline', { timeline: 'friends' })
// Start fetching notifications
store.dispatch('startFetchingNotifications')
// Get user mutes
store.dispatch('fetchMutes')
// Fetch our friends
store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
.then((friends) => commit('addNewUsers', friends))
} else {
const response = data.error
// Authentication failed
commit('endLogin')
if (response.status === 401) {
reject('Wrong username or password')
} else {
reject('An error occurred, please try again')
}
}
commit('endLogin')
resolve()
})
.catch((error) => {
console.log(error)
commit('endLogin')
reject('Failed to connect to server, try again')
})
})
}
}
}
export default users
diff --git a/src/services/follow_manipulate/follow_manipulate.js b/src/services/follow_manipulate/follow_manipulate.js
index 51dafe84ee..b2486e7c17 100644
--- a/src/services/follow_manipulate/follow_manipulate.js
+++ b/src/services/follow_manipulate/follow_manipulate.js
@@ -1,74 +1,62 @@
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('updateUserRelationship', [updated])
// For locked users we just mark it that we sent the follow request
if (updated.locked) {
- resolve({
- sent: true,
- updated
- })
+ resolve({ sent: true })
}
if (updated.following) {
// If we get result immediately, just stop.
- resolve({
- sent: false,
- updated
- })
+ resolve({ sent: false })
}
// 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
- })
+ resolve({ sent: false })
} else {
// If after all the tries, just treat it as if user is locked
- resolve({
- sent: false,
- updated
- })
+ resolve({ sent: false })
}
})
})
})
export const requestUnfollow = (user, store) => new Promise((resolve, reject) => {
store.state.api.backendInteractor.unfollowUser(user.id)
.then((updated) => {
store.commit('updateUserRelationship', [updated])
resolve({
updated
})
})
})
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 2, 9:47 AM (2 h, 55 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1804303
Default Alt Text
(24 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline