forked from AkkomaGang/akkoma-fe
Add list of followed hashtags to profile
This commit is contained in:
parent
0e71597e56
commit
79d506e331
7 changed files with 147 additions and 13 deletions
27
src/components/followed_tag_card/FollowedTagCard.vue
Normal file
27
src/components/followed_tag_card/FollowedTagCard.vue
Normal 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>
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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'))
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue