logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
commit: 9364964b017154f61072ffa70b08f2ed0c7dcaeb
parent: 92eb55c7bc3cf210b86259b99c3e2b8f192bf325
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date:   Fri, 15 Mar 2019 13:14:41 +0000

Merge branch 'mastoapi/user-stuff' into 'develop'

Transition to MastoAPI: user data

See merge request pleroma/pleroma-fe!655

Diffstat:

Msrc/components/block_card/block_card.js2+-
Msrc/components/mute_card/mute_card.js2+-
Msrc/components/status/status.js8++++----
Msrc/components/user_avatar/user_avatar.js5+++++
Msrc/components/user_card/user_card.js3+++
Msrc/components/user_profile/user_profile.js94+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/components/user_profile/user_profile.vue4++--
Msrc/modules/statuses.js6++++--
Msrc/modules/users.js36++++++++++++++++++++++++++++--------
Msrc/services/api/api.service.js33+++++++++++++++++++++++++--------
Msrc/services/backend_interactor_service/backend_interactor_service.js5+++++
Msrc/services/entity_normalizer/entity_normalizer.service.js27+++++++++++++++------------
Mtest/unit/specs/components/user_profile.spec.js14++++++++++----
Mtest/unit/specs/modules/users.spec.js35+++++++++++++----------------------
14 files changed, 167 insertions(+), 107 deletions(-)

diff --git a/src/components/block_card/block_card.js b/src/components/block_card/block_card.js @@ -9,7 +9,7 @@ const BlockCard = { }, computed: { user () { - return this.$store.getters.userById(this.userId) + return this.$store.getters.findUser(this.userId) }, blocked () { return this.user.statusnet_blocking diff --git a/src/components/mute_card/mute_card.js b/src/components/mute_card/mute_card.js @@ -9,7 +9,7 @@ const MuteCard = { }, computed: { user () { - return this.$store.getters.userById(this.userId) + return this.$store.getters.findUser(this.userId) }, muted () { return this.user.muted diff --git a/src/components/status/status.js b/src/components/status/status.js @@ -145,11 +145,11 @@ const Status = { return !!(this.status.in_reply_to_status_id && this.status.in_reply_to_user_id) }, replyToName () { - const user = this.$store.state.users.usersObject[this.status.in_reply_to_user_id] - if (user) { - return user.screen_name - } else { + if (this.status.in_reply_to_screen_name) { return this.status.in_reply_to_screen_name + } else { + const user = this.$store.getters.findUser(this.status.in_reply_to_user_id) + return user && user.screen_name } }, hideReply () { diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js @@ -23,6 +23,11 @@ const UserAvatar = { imageLoadError () { this.showPlaceholder = true } + }, + watch: { + src () { + this.showPlaceholder = false + } } } diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js @@ -15,6 +15,9 @@ export default { betterShadow: this.$store.state.interface.browserSupport.cssFilter } }, + created () { + this.$store.dispatch('fetchUserRelationship', this.user.id) + }, computed: { classes () { return [{ diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js @@ -9,7 +9,7 @@ import withList from '../../hocs/with_list/with_list' const FollowerList = compose( withLoadMore({ fetch: (props, $store) => $store.dispatch('addFollowers', props.userId), - select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []), + select: (props, $store) => get($store.getters.findUser(props.userId), 'followers', []), destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -20,7 +20,7 @@ const FollowerList = compose( const FriendList = compose( withLoadMore({ fetch: (props, $store) => $store.dispatch('addFriends', props.userId), - select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []), + select: (props, $store) => get($store.getters.findUser(props.userId), 'friends', []), destory: (props, $store) => $store.dispatch('clearFriends', props.userId), childPropName: 'entries', additionalPropNames: ['userId'] @@ -31,28 +31,16 @@ const FriendList = compose( const UserProfile = { data () { return { - error: false + error: false, + fetchedUserId: null } }, created () { - this.$store.commit('clearTimeline', { timeline: 'user' }) - this.$store.commit('clearTimeline', { timeline: 'favorites' }) - this.$store.commit('clearTimeline', { timeline: 'media' }) - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) - this.startFetchFavorites() if (!this.user.id) { - this.$store.dispatch('fetchUser', this.fetchBy) - .catch((reason) => { - const errorMessage = get(reason, 'error.error') - if (errorMessage === 'No user with such user_id') { // Known error - this.error = this.$t('user_profile.profile_does_not_exist') - } else if (errorMessage) { - this.error = errorMessage - } else { - this.error = this.$t('user_profile.profile_loading_error') - } - }) + this.fetchUserId() + .then(() => this.startUp()) + } else { + this.startUp() } }, destroyed () { @@ -69,7 +57,7 @@ const UserProfile = { return this.$store.state.statuses.timelines.media }, userId () { - return this.$route.params.id || this.user.id + return this.$route.params.id || this.user.id || this.fetchedUserId }, userName () { return this.$route.params.name || this.user.screen_name @@ -79,10 +67,9 @@ const UserProfile = { this.userId === this.$store.state.users.currentUser.id }, userInStore () { - if (this.isExternal) { - return this.$store.getters.userById(this.userId) - } - return this.$store.getters.userByName(this.userName) + const routeParams = this.$route.params + // This needs fetchedUserId so that computed will be refreshed when user is fetched + return this.$store.getters.findUser(this.fetchedUserId || routeParams.name || routeParams.id) }, user () { if (this.timeline.statuses[0]) { @@ -93,9 +80,6 @@ const UserProfile = { } return {} }, - fetchBy () { - return this.isExternal ? this.userId : this.userName - }, isExternal () { return this.$route.name === 'external-user-profile' }, @@ -109,14 +93,38 @@ const UserProfile = { methods: { startFetchFavorites () { if (this.isUs) { - this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.fetchBy }) + this.$store.dispatch('startFetching', { timeline: 'favorites', userId: this.userId }) + } + }, + fetchUserId () { + let fetchPromise + if (this.userId && !this.$route.params.name) { + fetchPromise = this.$store.dispatch('fetchUser', this.userId) + } else { + fetchPromise = this.$store.dispatch('fetchUser', this.userName) + .then(({ id }) => { + this.fetchedUserId = id + }) } + return fetchPromise + .catch((reason) => { + const errorMessage = get(reason, 'error.error') + if (errorMessage === 'No user with such user_id') { // Known error + this.error = this.$t('user_profile.profile_does_not_exist') + } else if (errorMessage) { + this.error = errorMessage + } else { + this.error = this.$t('user_profile.profile_loading_error') + } + }) + .then(() => this.startUp()) }, startUp () { - this.$store.dispatch('startFetching', { timeline: 'user', userId: this.fetchBy }) - this.$store.dispatch('startFetching', { timeline: 'media', userId: this.fetchBy }) - - this.startFetchFavorites() + if (this.userId) { + this.$store.dispatch('startFetching', { timeline: 'user', userId: this.userId }) + this.$store.dispatch('startFetching', { timeline: 'media', userId: this.userId }) + this.startFetchFavorites() + } }, cleanUp () { this.$store.dispatch('stopFetching', 'user') @@ -128,19 +136,19 @@ const UserProfile = { } }, watch: { - userName () { - if (this.isExternal) { - return + // userId can be undefined if we don't know it yet + userId (newVal) { + if (newVal) { + this.cleanUp() + this.startUp() } - this.cleanUp() - this.startUp() }, - userId () { - if (!this.isExternal) { - return + userName () { + if (this.$route.params.name) { + this.fetchUserId() + this.cleanUp() + this.startUp() } - this.cleanUp() - this.startUp() }, $route () { this.$refs.tabSwitcher.activateTab(0)() diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue @@ -11,7 +11,7 @@ :title="$t('user_profile.timeline_title')" :timeline="timeline" :timeline-name="'user'" - :user-id="fetchBy" + :user-id="userId" /> <div :label="$t('user_card.followees')" v-if="followsTabVisible" :disabled="!user.friends_count"> <FriendList :userId="userId" /> @@ -25,7 +25,7 @@ :embedded="true" :title="$t('user_card.media')" timeline-name="media" :timeline="media" - :user-id="fetchBy" + :user-id="userId" /> <Timeline v-if="isUs" diff --git a/src/modules/statuses.js b/src/modules/statuses.js @@ -1,4 +1,4 @@ -import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray } from 'lodash' +import { remove, slice, each, find, maxBy, minBy, merge, first, last, isArray, omitBy } from 'lodash' import apiService from '../services/api/api.service.js' // import parse from '../services/status_parser/status_parser.js' @@ -72,7 +72,9 @@ const mergeOrAdd = (arr, obj, item) => { if (oldItem) { // We already have this, so only merge the new info. - merge(oldItem, item) + // We ignore null values to avoid overwriting existing properties with missing data + // we also skip 'user' because that is handled by users module + merge(oldItem, omitBy(item, (v, k) => v === null || k === 'user')) // Reactivity fix. oldItem.attachments.splice(oldItem.attachments.length) return {item: oldItem, new: false} diff --git a/src/modules/users.js b/src/modules/users.js @@ -18,7 +18,7 @@ export const mergeOrAdd = (arr, obj, item) => { arr.push(item) obj[item.id] = item if (item.screen_name && !item.screen_name.includes('@')) { - obj[item.screen_name] = item + obj[item.screen_name.toLowerCase()] = item } return { item, new: true } } @@ -91,6 +91,17 @@ export const mutations = { 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 + } + }) + }, saveBlocks (state, blockIds) { state.currentUser.blockIds = blockIds }, @@ -122,12 +133,14 @@ export const mutations = { } export const getters = { - userById: state => id => - state.users.find(user => user.id === id), - userByName: state => name => - state.users.find(user => user.screen_name && - (user.screen_name.toLowerCase() === name.toLowerCase()) - ) + 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 = { @@ -147,7 +160,14 @@ const users = { actions: { fetchUser (store, id) { return store.rootState.api.backendInteractor.fetchUser({ id }) - .then((user) => store.commit('addNewUsers', [user])) + .then((user) => { + store.commit('addNewUsers', [user]) + return user + }) + }, + fetchUserRelationship (store, id) { + return store.rootState.api.backendInteractor.fetchUserRelationship({ id }) + .then((relationships) => store.commit('updateUserRelationship', relationships)) }, fetchBlocks (store) { return store.rootState.api.backendInteractor.fetchBlocks() diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js @@ -28,12 +28,10 @@ const BG_UPDATE_URL = '/api/qvitter/update_background_image.json' const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json' const PROFILE_UPDATE_URL = '/api/account/update_profile.json' const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json' -const QVITTER_USER_TIMELINE_URL = '/api/qvitter/statuses/user_timeline.json' const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json' const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json' const BLOCKING_URL = '/api/blocks/create.json' const UNBLOCKING_URL = '/api/blocks/destroy.json' -const USER_URL = '/api/users/show.json' const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import' const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' @@ -43,6 +41,9 @@ const DENY_USER_URL = '/api/pleroma/friendships/deny' const SUGGESTIONS_URL = '/api/v1/suggestions' const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites' +const MASTODON_USER_URL = '/api/v1/accounts' +const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships' +const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses` import { each, map } from 'lodash' import { parseStatus, parseUser, parseNotification } from '../entity_normalizer/entity_normalizer.service.js' @@ -243,7 +244,7 @@ const denyUser = ({id, credentials}) => { } const fetchUser = ({id, credentials}) => { - let url = `${USER_URL}?user_id=${id}` + let url = `${MASTODON_USER_URL}/${id}` return fetch(url, { headers: authHeaders(credentials) }) .then((response) => { return new Promise((resolve, reject) => response.json() @@ -257,6 +258,20 @@ const fetchUser = ({id, credentials}) => { .then((data) => parseUser(data)) } +const fetchUserRelationship = ({id, credentials}) => { + let url = `${MASTODON_USER_RELATIONSHIPS_URL}/?id=${id}` + return fetch(url, { headers: authHeaders(credentials) }) + .then((response) => { + return new Promise((resolve, reject) => response.json() + .then((json) => { + if (!response.ok) { + return reject(new StatusCodeError(response.status, json, { url }, response)) + } + return resolve(json) + })) + }) +} + const fetchFriends = ({id, page, credentials}) => { let url = `${FRIENDS_URL}?user_id=${id}` if (page) { @@ -347,8 +362,8 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use dms: DM_TIMELINE_URL, notifications: QVITTER_USER_NOTIFICATIONS_URL, 'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL, - user: QVITTER_USER_TIMELINE_URL, - media: QVITTER_USER_TIMELINE_URL, + user: MASTODON_USER_TIMELINE_URL, + media: MASTODON_USER_TIMELINE_URL, favorites: MASTODON_USER_FAVORITES_TIMELINE_URL, tag: TAG_TIMELINE_URL } @@ -357,15 +372,16 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use let url = timelineUrls[timeline] + if (timeline === 'user' || timeline === 'media') { + url = url(userId) + } + if (since) { params.push(['since_id', since]) } if (until) { params.push(['max_id', until]) } - if (userId) { - params.push(['user_id', userId]) - } if (tag) { url += `/${tag}.json` } @@ -588,6 +604,7 @@ const apiService = { blockUser, unblockUser, fetchUser, + fetchUserRelationship, favorite, unfavorite, retweet, diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js @@ -30,6 +30,10 @@ const backendInteractorService = (credentials) => { return apiService.fetchUser({id, credentials}) } + const fetchUserRelationship = ({id}) => { + return apiService.fetchUserRelationship({id, credentials}) + } + const followUser = (id) => { return apiService.followUser({credentials, id}) } @@ -92,6 +96,7 @@ const backendInteractorService = (credentials) => { blockUser, unblockUser, fetchUser, + fetchUserRelationship, fetchAllFollowing, verifyCredentials: apiService.verifyCredentials, startFetching, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js @@ -39,10 +39,10 @@ export const parseUser = (data) => { return output } - output.name = null // missing + // output.name = ??? missing output.name_html = data.display_name - output.description = null // missing + // output.description = ??? missing output.description_html = data.note // Utilize avatar_static for gif avatars? @@ -59,10 +59,14 @@ export const parseUser = (data) => { output.statusnet_profile_url = data.url if (data.pleroma) { - const pleroma = data.pleroma - output.follows_you = pleroma.follows_you - output.statusnet_blocking = pleroma.statusnet_blocking - output.muted = pleroma.muted + const relationship = data.pleroma.relationship + + if (relationship) { + output.follows_you = relationship.followed_by + output.following = relationship.following + output.statusnet_blocking = relationship.blocking + output.muted = relationship.muting + } } // Missing, trying to recover @@ -83,7 +87,7 @@ export const parseUser = (data) => { output.friends_count = data.friends_count - output.bot = null // missing + // output.bot = ??? missing output.statusnet_profile_url = data.statusnet_profile_url @@ -134,7 +138,7 @@ const parseAttachment = (data) => { output.meta = data.meta // not present in BE yet } else { output.mimetype = data.mimetype - output.meta = null // missing + // output.meta = ??? missing } output.url = data.url @@ -166,7 +170,7 @@ export const parseStatus = (data) => { output.in_reply_to_user_id = data.in_reply_to_account_id // Missing!! fix in UI? - output.in_reply_to_screen_name = null + // output.in_reply_to_screen_name = ??? // Not exactly the same but works output.statusnet_conversation_id = data.id @@ -179,8 +183,7 @@ export const parseStatus = (data) => { output.summary_html = data.spoiler_text output.external_url = data.url - // FIXME missing!! - output.is_local = false + // output.is_local = ??? missing } else { output.favorited = data.favorited output.fave_num = data.fave_num @@ -259,7 +262,7 @@ export const parseNotification = (data) => { if (masto) { output.type = mastoDict[data.type] || data.type - output.seen = null // missing + // output.seen = ??? missing output.status = parseStatus(data.status) output.action = output.status // not sure output.from_profile = parseUser(data.account) diff --git a/test/unit/specs/components/user_profile.spec.js b/test/unit/specs/components/user_profile.spec.js @@ -12,9 +12,13 @@ const mutations = { setError: () => {} } +const actions = { + fetchUser: () => {}, + fetchUserByScreenName: () => {} +} + const testGetters = { - userByName: state => getters.userByName(state.users), - userById: state => getters.userById(state.users) + findUser: state => getters.findUser(state.users) } const localUser = { @@ -31,6 +35,7 @@ const extUser = { const externalProfileStore = new Vuex.Store({ mutations, + actions, getters: testGetters, state: { api: { @@ -89,7 +94,7 @@ const externalProfileStore = new Vuex.Store({ currentUser: { credentials: '' }, - usersObject: [extUser], + usersObject: { 100: extUser }, users: [extUser] } } @@ -97,6 +102,7 @@ const externalProfileStore = new Vuex.Store({ const localProfileStore = new Vuex.Store({ mutations, + actions, getters: testGetters, state: { api: { @@ -155,7 +161,7 @@ const localProfileStore = new Vuex.Store({ currentUser: { credentials: '' }, - usersObject: [localUser], + usersObject: { 100: localUser, 'testuser': localUser }, users: [localUser] } } diff --git a/test/unit/specs/modules/users.spec.js b/test/unit/specs/modules/users.spec.js @@ -34,40 +34,31 @@ describe('The users module', () => { }) }) - describe('getUserByName', () => { + describe('findUser', () => { it('returns user with matching screen_name', () => { + const user = { screen_name: 'Guy', id: '1' } const state = { - users: [ - { screen_name: 'Guy', id: '1' } - ] + usersObject: { + 1: user, + guy: user + } } const name = 'Guy' const expected = { screen_name: 'Guy', id: '1' } - expect(getters.userByName(state)(name)).to.eql(expected) + expect(getters.findUser(state)(name)).to.eql(expected) }) - it('returns user with matching screen_name with different case', () => { - const state = { - users: [ - { screen_name: 'guy', id: '1' } - ] - } - const name = 'Guy' - const expected = { screen_name: 'guy', id: '1' } - expect(getters.userByName(state)(name)).to.eql(expected) - }) - }) - - describe('getUserById', () => { it('returns user with matching id', () => { + const user = { screen_name: 'Guy', id: '1' } const state = { - users: [ - { screen_name: 'Guy', id: '1' } - ] + usersObject: { + 1: user, + guy: user + } } const id = '1' const expected = { screen_name: 'Guy', id: '1' } - expect(getters.userById(state)(id)).to.eql(expected) + expect(getters.findUser(state)(id)).to.eql(expected) }) }) })