diff --git a/src/components/followed_tag_card/FollowedTagCard.vue b/src/components/followed_tag_card/FollowedTagCard.vue
new file mode 100644
index 00000000..3ce5d8e9
--- /dev/null
+++ b/src/components/followed_tag_card/FollowedTagCard.vue
@@ -0,0 +1,27 @@
+
+
+
+
+ #{{ tag.name }}
+
+
+
+
+
+
+
+
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index 8fba1a28..702148c1 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -13,6 +13,7 @@ import {
faCircleNotch,
faCircleCheck
} from '@fortawesome/free-solid-svg-icons'
+import FollowedTagCard from '../followed_tag_card/FollowedTagCard.vue'
library.add(
faCircleNotch,
@@ -35,6 +36,14 @@ const FriendList = withLoadMore({
additionalPropNames: ['userId']
})(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 UserProfile = {
@@ -202,6 +211,7 @@ const UserProfile = {
}
},
components: {
+ FollowedTagCard,
UserCard,
Timeline,
FollowerList,
@@ -209,7 +219,8 @@ const UserProfile = {
FollowCard,
TabSwitcher,
Conversation,
- RichContent
+ RichContent,
+ FollowedTagList,
}
}
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index 87bbf679..e657b8a4 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -105,11 +105,36 @@
key="followees"
:label="$t('user_card.followees')"
>
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ .catch((e) => {
+ console.error(e)
this.loading = false
this.error = true
})
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 575c3dfa..ee7c53a6 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1139,6 +1139,8 @@
"follow_unfollow": "Unfollow this jerk",
"followees": "Following",
"followers": "Followers",
+ "followed_tags": "Followed hashtags",
+ "followed_users": "Followed users",
"following": "Following!",
"follows_you": "Follows you!",
"hidden": "Hidden",
diff --git a/src/modules/users.js b/src/modules/users.js
index 022cc1dc..456aa746 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -5,9 +5,9 @@ import { compact, map, each, mergeWith, last, concat, uniq, isArray } from 'loda
import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.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 }
- const oldItem = obj[item.id]
+ const oldItem = obj[item[key]]
if (oldItem) {
// We already have this, so only merge the new info.
mergeWith(oldItem, item, mergeArrayLength)
@@ -15,7 +15,7 @@ export const mergeOrAdd = (arr, obj, item) => {
} else {
// This is a new item, prepare it
arr.push(item)
- obj[item.id] = item
+ obj[item[key]] = item
if (item.screen_name && !item.screen_name.includes('@')) {
obj[item.screen_name.toLowerCase()] = item
}
@@ -157,6 +157,14 @@ export const mutations = {
const user = state.usersObject[id]
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
// outside of viewing someones user profile.
clearFriends (state, userId) {
@@ -171,6 +179,12 @@ export const mutations = {
user['followerIds'] = []
}
},
+ clearFollowedTags (state, userId) {
+ const user = state.usersObject[userId]
+ if (user) {
+ user['followedTagIds'] = []
+ }
+ },
addNewUsers (state, users) {
each(users, (user) => {
if (user.relationship) {
@@ -179,6 +193,11 @@ export const mutations = {
mergeOrAdd(state.users, state.usersObject, user)
})
},
+ addNewTags (state, tags) {
+ each(tags, (tag) => {
+ mergeOrAdd(state.tags, state.tagsObject, tag, 'name')
+ })
+ },
updateUserRelationship (state, relationships) {
relationships.forEach((relationship) => {
state.relationships[relationship.id] = relationship
@@ -271,7 +290,11 @@ export const getters = {
relationship: state => id => {
const rel = id && state.relationships[id]
return rel || { id, loading: true }
- }
+ },
+ findTag: state => query => {
+ const result = state.tagsObject[query]
+ return result
+ },
}
export const defaultState = {
@@ -282,7 +305,9 @@ export const defaultState = {
usersObject: {},
signUpPending: false,
signUpErrors: [],
- relationships: {}
+ relationships: {},
+ tags: [],
+ tagsObject: {}
}
const users = {
@@ -402,12 +427,27 @@ const users = {
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) {
commit('clearFriends', userId)
},
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
},
+ clearFollowedTags ({ commit }, userId) {
+ commit('clearFollowedTags', userId)
+ },
subscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.subscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship]))
@@ -437,6 +477,9 @@ const users = {
addNewUsers ({ commit }, users) {
commit('addNewUsers', users)
},
+ addNewTags ({ commit }, tags) {
+ commit('addNewTags', tags)
+ },
addNewStatuses (store, { statuses }) {
const users = map(statuses, 'user')
const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 21c4b25b..b7174b68 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -1,6 +1,7 @@
import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseSource, parseUser, parseNotification, parseReport, parseAttachment, parseLinkHeaderPagination } from '../entity_normalizer/entity_normalizer.service.js'
import { RegistrationError, StatusCodeError } from '../errors/errors'
+import { Url } from 'url'
/* eslint-env browser */
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_FOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/follow`
const MASTODON_UNFOLLOW_TAG_URL = (name) => `/api/v1/tags/${name}/unfollow`
+const MASTODON_FOLLOWED_TAGS_URL = '/api/v1/followed_tags'
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 = {} }) => {
return Object.entries({
...(credentials
@@ -1813,7 +1837,8 @@ const apiService = {
deleteNoteFromReport,
getHashtag,
followHashtag,
- unfollowHashtag
+ unfollowHashtag,
+ getFollowedHashtags,
}
export default apiService