From e9f16af82d04d6f800053c98baf3fca0df453f4e Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 1 Jan 2023 20:11:07 +0000 Subject: [PATCH] Add list of followed hashtags to profile --- .../followed_tag_card/FollowedTagCard.vue | 27 ++++++++++ src/components/user_profile/user_profile.js | 13 ++++- src/components/user_profile/user_profile.vue | 35 ++++++++++-- src/hocs/with_load_more/with_load_more.jsx | 3 +- src/i18n/en.json | 2 + src/modules/users.js | 53 +++++++++++++++++-- src/services/api/api.service.js | 27 +++++++++- 7 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 src/components/followed_tag_card/FollowedTagCard.vue 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 @@ + + + + + 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 46b5bad3..8d9c6444 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1139,6 +1139,8 @@ "follow_unfollow": "Unfollow", "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