Add list of followed hashtags to profile

This commit is contained in:
FloatingGhost 2023-01-01 20:11:07 +00:00 committed by hynet-mel
parent 0e71597e56
commit 79d506e331
7 changed files with 147 additions and 13 deletions

View file

@ -0,0 +1,27 @@
<template>
<div class="followed-tag-card">
<h3>
<router-link :to="{ name: 'tag-timeline', params: {tag: tag.name}}">
#{{ tag.name }}
</router-link>
</h3>
</div>
</template>
<script>
export default {
name: 'FollowedTagCard',
props: {
tag: {
type: Object,
required: true
}
},
}
</script>
<style scoped>
.followed-tag-card {
margin-left: 1rem;
}
</style>

View file

@ -13,6 +13,7 @@ import {
faCircleNotch, faCircleNotch,
faCircleCheck faCircleCheck
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import FollowedTagCard from '../followed_tag_card/FollowedTagCard.vue'
library.add( library.add(
faCircleNotch, faCircleNotch,
@ -35,6 +36,14 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId'] additionalPropNames: ['userId']
})(List) })(List)
const FollowedTagList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchFollowedTags', props.userId),
select: (props, $store) => get($store.getters.findUser(props.userId), 'followedTagIds', []).map(id => $store.getters.findTag(id)),
destroy: (props, $store) => $store.dispatch('clearFollowedTags', props.userId),
childPropName: 'items',
additionalPropNames: ['userId']
})(List)
const isUserPage = ({ name }) => name === 'user-profile' || name === 'external-user-profile' const isUserPage = ({ name }) => name === 'user-profile' || name === 'external-user-profile'
const UserProfile = { const UserProfile = {
@ -202,6 +211,7 @@ const UserProfile = {
} }
}, },
components: { components: {
FollowedTagCard,
UserCard, UserCard,
Timeline, Timeline,
FollowerList, FollowerList,
@ -209,7 +219,8 @@ const UserProfile = {
FollowCard, FollowCard,
TabSwitcher, TabSwitcher,
Conversation, Conversation,
RichContent RichContent,
FollowedTagList,
} }
} }

View file

@ -105,11 +105,36 @@
key="followees" key="followees"
:label="$t('user_card.followees')" :label="$t('user_card.followees')"
> >
<FriendList :user-id="userId"> <tab-switcher
<template #item="{item}"> :active-tab="users"
<FollowCard :user="item" /> :render-only-focused="true"
</template> >
</FriendList> <div
key="users"
:label="$t('user_card.followed_users')"
>
<FriendList :user-id="userId">
<template #item="{item}">
<FollowCard :user="item" />
</template>
</FriendList>
</div>
<div
key="tags"
v-if="isUs"
:label="$t('user_card.followed_tags')"
>
<FollowedTagList
:user-id="userId"
:following="false"
:get-key="(item) => item.name"
>
<template #item="{item}">
<FollowedTagCard :tag="item" />
</template>
</FollowedTagList>
</div>
</tab-switcher>
</div> </div>
<div <div
v-if="followersTabVisible" v-if="followersTabVisible"

View file

@ -59,7 +59,8 @@ const withLoadMore = ({
this.loading = false this.loading = false
this.bottomedOut = isEmpty(newEntries) this.bottomedOut = isEmpty(newEntries)
}) })
.catch(() => { .catch((e) => {
console.error(e)
this.loading = false this.loading = false
this.error = true this.error = true
}) })

View file

@ -1139,6 +1139,8 @@
"follow_unfollow": "Unfollow this jerk", "follow_unfollow": "Unfollow this jerk",
"followees": "Following", "followees": "Following",
"followers": "Followers", "followers": "Followers",
"followed_tags": "Followed hashtags",
"followed_users": "Followed users",
"following": "Following!", "following": "Following!",
"follows_you": "Follows you!", "follows_you": "Follows you!",
"hidden": "Hidden", "hidden": "Hidden",

View file

@ -5,9 +5,9 @@ import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'loda
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js' import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
// TODO: Unify with mergeOrAdd in statuses.js // TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => { export const mergeOrAdd = (arr, obj, item, key = 'id') => {
if (!item) { return false } if (!item) { return false }
const oldItem = obj[item.id] const oldItem = obj[item[key]]
if (oldItem) { if (oldItem) {
// We already have this, so only merge the new info. // We already have this, so only merge the new info.
mergeWith(oldItem, item, mergeArrayLength) mergeWith(oldItem, item, mergeArrayLength)
@ -15,7 +15,7 @@ export const mergeOrAdd = (arr, obj, item) => {
} else { } else {
// This is a new item, prepare it // This is a new item, prepare it
arr.push(item) arr.push(item)
obj[item.id] = item obj[item[key]] = item
if (item.screen_name && !item.screen_name.includes('@')) { if (item.screen_name && !item.screen_name.includes('@')) {
obj[item.screen_name.toLowerCase()] = item obj[item.screen_name.toLowerCase()] = item
} }
@ -157,6 +157,14 @@ export const mutations = {
const user = state.usersObject[id] const user = state.usersObject[id]
user.followerIds = uniq(concat(user.followerIds || [], followerIds)) user.followerIds = uniq(concat(user.followerIds || [], followerIds))
}, },
saveFollowedTagIds (state, { id, followedTagIds }) {
const user = state.usersObject[id]
user.followedTagIds = uniq(concat(user.followedTagIds || [], followedTagIds))
},
saveFollowedTagPagination (state, { id, pagination }) {
const user = state.usersObject[id]
user.followedTagPagination = pagination
},
// Because frontend doesn't have a reason to keep these stuff in memory // Because frontend doesn't have a reason to keep these stuff in memory
// outside of viewing someones user profile. // outside of viewing someones user profile.
clearFriends (state, userId) { clearFriends (state, userId) {
@ -171,6 +179,12 @@ export const mutations = {
user['followerIds'] = [] user['followerIds'] = []
} }
}, },
clearFollowedTags (state, userId) {
const user = state.usersObject[userId]
if (user) {
user['followedTagIds'] = []
}
},
addNewUsers (state, users) { addNewUsers (state, users) {
each(users, (user) => { each(users, (user) => {
if (user.relationship) { if (user.relationship) {
@ -179,6 +193,11 @@ export const mutations = {
mergeOrAdd(state.users, state.usersObject, user) mergeOrAdd(state.users, state.usersObject, user)
}) })
}, },
addNewTags (state, tags) {
each(tags, (tag) => {
mergeOrAdd(state.tags, state.tagsObject, tag, 'name')
})
},
updateUserRelationship (state, relationships) { updateUserRelationship (state, relationships) {
relationships.forEach((relationship) => { relationships.forEach((relationship) => {
state.relationships[relationship.id] = relationship state.relationships[relationship.id] = relationship
@ -271,7 +290,11 @@ export const getters = {
relationship: state => id => { relationship: state => id => {
const rel = id && state.relationships[id] const rel = id && state.relationships[id]
return rel || { id, loading: true } return rel || { id, loading: true }
} },
findTag: state => query => {
const result = state.tagsObject[query]
return result
},
} }
export const defaultState = { export const defaultState = {
@ -282,7 +305,9 @@ export const defaultState = {
usersObject: {}, usersObject: {},
signUpPending: false, signUpPending: false,
signUpErrors: [], signUpErrors: [],
relationships: {} relationships: {},
tags: [],
tagsObject: {}
} }
const users = { const users = {
@ -402,12 +427,27 @@ const users = {
return followers return followers
}) })
}, },
fetchFollowedTags ({ rootState, commit }, id) {
const user = rootState.users.usersObject[id]
const pagination = user.followedTagPagination
return rootState.api.backendInteractor.getFollowedHashtags({ pagination })
.then(({ data: tags, pagination }) => {
commit('addNewTags', tags)
commit('saveFollowedTagIds', { id, followedTagIds: tags.map(tag => tag.name) })
commit('saveFollowedTagPagination', { id, pagination })
return tags
})
},
clearFriends ({ commit }, userId) { clearFriends ({ commit }, userId) {
commit('clearFriends', userId) commit('clearFriends', userId)
}, },
clearFollowers ({ commit }, userId) { clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId) commit('clearFollowers', userId)
}, },
clearFollowedTags ({ commit }, userId) {
commit('clearFollowedTags', userId)
},
subscribeUser ({ rootState, commit }, id) { subscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.subscribeUser({ id }) return rootState.api.backendInteractor.subscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship])) .then((relationship) => commit('updateUserRelationship', [relationship]))
@ -437,6 +477,9 @@ const users = {
addNewUsers ({ commit }, users) { addNewUsers ({ commit }, users) {
commit('addNewUsers', users) commit('addNewUsers', users)
}, },
addNewTags ({ commit }, tags) {
commit('addNewTags', tags)
},
addNewStatuses (store, { statuses }) { addNewStatuses (store, { statuses }) {
const users = map(statuses, 'user') const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user')) const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))

View file

@ -1,6 +1,7 @@
import { each, map, concat, last, get } from 'lodash' import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js' import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors' import { RegistrationError, StatusCodeError } from '../errors/errors'
import { Url } from 'url'
/* eslint-env browser */ /* eslint-env browser */
const MUTES_IMPORT_URL = '/api/pleroma/mutes_import' const MUTES_IMPORT_URL = '/api/pleroma/mutes_import'
@ -111,6 +112,7 @@ const AKKOMA_SETTING_PROFILE_LIST = `/api/v1/akkoma/frontend_settings/pleroma-fe
const MASTODON_TAG_URL = (name) => `/api/v1/tags/${name}` const MASTODON_TAG_URL = (name) => `/api/v1/tags/${name}`
const MASTODON_FOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/follow` const MASTODON_FOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/follow`
const MASTODON_UNFOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/unfollow` const MASTODON_UNFOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/unfollow`
const MASTODON_FOLLOWED_TAGS_URL = '/api/v1/followed_tags'
const oldfetch = window.fetch const oldfetch = window.fetch
@ -1575,6 +1577,28 @@ const unfollowHashtag = ({ tag, credentials }) => {
}) })
} }
const getFollowedHashtags = ({ credentials, pagination: savedPagination }) => {
const queryParams = new URLSearchParams()
if (savedPagination?.maxId) {
queryParams.append('max_id', savedPagination.maxId)
}
const url = `${MASTODON_FOLLOWED_TAGS_URL}?${queryParams.toString()}`
let pagination = {};
return fetch(url, {
credentials
}).then((data) => {
pagination = parseLinkHeaderPagination(data.headers.get('Link'), {
flakeId: false
});
return data.json()
}).then((data) => {
return {
pagination,
data
}
});
}
export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => { export const getMastodonSocketURI = ({ credentials, stream, args = {} }) => {
return Object.entries({ return Object.entries({
...(credentials ...(credentials
@ -1813,7 +1837,8 @@ const apiService = {
deleteNoteFromReport, deleteNoteFromReport,
getHashtag, getHashtag,
followHashtag, followHashtag,
unfollowHashtag unfollowHashtag,
getFollowedHashtags,
} }
export default apiService export default apiService