diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue index 4ede15e9..77fb0aa0 100644 --- a/src/components/basic_user_card/basic_user_card.vue +++ b/src/components/basic_user_card/basic_user_card.vue @@ -1,26 +1,22 @@ @@ -46,30 +42,21 @@ margin-left: 0.7em; text-align: left; flex: 1; - display: flex; - align-items: flex-start; - justify-content: space-between; + min-width: 0; } - &-primary-area { - flex: 1; - .user-name { - img { - object-fit: contain; - height: 16px; - width: 16px; - vertical-align: middle; - } + &-user-name { + img { + object-fit: contain; + height: 16px; + width: 16px; + vertical-align: middle; } } - &-secondary-area { - flex: none; - } - &-expanded-content { flex: 1; - margin: 0.2em 0 0 0.7em; + margin-left: 0.7em; border-radius: $fallback--panelRadius; border-radius: var(--panelRadius, $fallback--panelRadius); border-style: solid; diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue index ed7fe30b..8eb56e25 100644 --- a/src/components/block_card/block_card.vue +++ b/src/components/block_card/block_card.vue @@ -1,6 +1,6 @@ - \ No newline at end of file + + + diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js new file mode 100644 index 00000000..425c9c3e --- /dev/null +++ b/src/components/follow_card/follow_card.js @@ -0,0 +1,45 @@ +import BasicUserCard from '../basic_user_card/basic_user_card.vue' +import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' + +const FollowCard = { + props: [ + 'user', + 'noFollowsYou' + ], + data () { + return { + inProgress: false, + requestSent: false, + updated: false + } + }, + components: { + BasicUserCard + }, + computed: { + isMe () { return this.$store.state.users.currentUser.id === this.user.id }, + following () { return this.updated ? this.updated.following : this.user.following }, + showFollow () { + return !this.following || this.updated && !this.updated.following + } + }, + methods: { + followUser () { + this.inProgress = true + requestFollow(this.user, this.$store).then(({ sent, updated }) => { + this.inProgress = false + this.requestSent = sent + this.updated = updated + }) + }, + unfollowUser () { + this.inProgress = true + requestUnfollow(this.user, this.$store).then(({ updated }) => { + this.inProgress = false + this.updated = updated + }) + } + } +} + +export default FollowCard diff --git a/src/components/follow_card/follow_card.vue b/src/components/follow_card/follow_card.vue new file mode 100644 index 00000000..6cb064eb --- /dev/null +++ b/src/components/follow_card/follow_card.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/components/follow_list/follow_list.js b/src/components/follow_list/follow_list.js deleted file mode 100644 index 9777c87e..00000000 --- a/src/components/follow_list/follow_list.js +++ /dev/null @@ -1,68 +0,0 @@ -import UserCard from '../user_card/user_card.vue' - -const FollowList = { - data () { - return { - loading: false, - bottomedOut: false, - error: false - } - }, - props: ['userId', 'showFollowers'], - created () { - window.addEventListener('scroll', this.scrollLoad) - if (this.entries.length === 0) { - this.fetchEntries() - } - }, - destroyed () { - window.removeEventListener('scroll', this.scrollLoad) - this.$store.dispatch('clearFriendsAndFollowers', this.userId) - }, - computed: { - user () { - return this.$store.getters.userById(this.userId) - }, - entries () { - return this.showFollowers ? this.user.followers : this.user.friends - }, - showFollowsYou () { - return !this.showFollowers || (this.showFollowers && this.userId !== this.$store.state.users.currentUser.id) - } - }, - methods: { - fetchEntries () { - if (!this.loading) { - const command = this.showFollowers ? 'addFollowers' : 'addFriends' - this.loading = true - this.$store.dispatch(command, this.userId).then(entries => { - this.error = false - this.loading = false - this.bottomedOut = entries.length === 0 - }).catch(() => { - this.error = true - this.loading = false - }) - } - }, - scrollLoad (e) { - const bodyBRect = document.body.getBoundingClientRect() - const height = Math.max(bodyBRect.height, -(bodyBRect.y)) - if (this.loading === false && - this.bottomedOut === false && - this.$el.offsetHeight > 0 && - (window.innerHeight + window.pageYOffset) >= (height - 750) - ) { - this.fetchEntries() - } - } - }, - watch: { - 'user': 'fetchEntries' - }, - components: { - UserCard - } -} - -export default FollowList diff --git a/src/components/follow_list/follow_list.vue b/src/components/follow_list/follow_list.vue deleted file mode 100644 index 27102edf..00000000 --- a/src/components/follow_list/follow_list.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - - - diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js new file mode 100644 index 00000000..1a00a1c1 --- /dev/null +++ b/src/components/follow_request_card/follow_request_card.js @@ -0,0 +1,20 @@ +import BasicUserCard from '../basic_user_card/basic_user_card.vue' + +const FollowRequestCard = { + props: ['user'], + components: { + BasicUserCard + }, + methods: { + approveUser () { + this.$store.state.api.backendInteractor.approveUser(this.user.id) + this.$store.dispatch('removeFollowRequest', this.user) + }, + denyUser () { + this.$store.state.api.backendInteractor.denyUser(this.user.id) + this.$store.dispatch('removeFollowRequest', this.user) + } + } +} + +export default FollowRequestCard diff --git a/src/components/follow_request_card/follow_request_card.vue b/src/components/follow_request_card/follow_request_card.vue new file mode 100644 index 00000000..4a3bbba4 --- /dev/null +++ b/src/components/follow_request_card/follow_request_card.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/components/follow_requests/follow_requests.js b/src/components/follow_requests/follow_requests.js index 11a228aa..d57dcbc7 100644 --- a/src/components/follow_requests/follow_requests.js +++ b/src/components/follow_requests/follow_requests.js @@ -1,8 +1,8 @@ -import UserCard from '../user_card/user_card.vue' +import FollowRequestCard from '../follow_request_card/follow_request_card.vue' const FollowRequests = { components: { - UserCard + FollowRequestCard }, created () { this.updateRequests() diff --git a/src/components/follow_requests/follow_requests.vue b/src/components/follow_requests/follow_requests.vue index 87dc4194..b83c2d68 100644 --- a/src/components/follow_requests/follow_requests.vue +++ b/src/components/follow_requests/follow_requests.vue @@ -4,7 +4,7 @@ {{$t('nav.friend_requests')}}
- +
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 62536bc5..d986663e 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -1,7 +1,6 @@ import Status from '../status/status.vue' import timelineFetcher from '../../services/timeline_fetcher/timeline_fetcher.service.js' import StatusOrConversation from '../status_or_conversation/status_or_conversation.vue' -import UserCard from '../user_card/user_card.vue' import { throttle } from 'lodash' const Timeline = { @@ -44,8 +43,7 @@ const Timeline = { }, components: { Status, - StatusOrConversation, - UserCard + StatusOrConversation }, created () { const store = this.$store diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js deleted file mode 100644 index 28e22f09..00000000 --- a/src/components/user_card/user_card.js +++ /dev/null @@ -1,64 +0,0 @@ -import UserCardContent from '../user_card_content/user_card_content.vue' -import UserAvatar from '../user_avatar/user_avatar.vue' -import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' -import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate' - -const UserCard = { - props: [ - 'user', - 'noFollowsYou', - 'showApproval' - ], - data () { - return { - userExpanded: false, - followRequestInProgress: false, - followRequestSent: false, - updated: false - } - }, - components: { - UserCardContent, - UserAvatar - }, - computed: { - currentUser () { return this.$store.state.users.currentUser }, - following () { return this.updated ? this.updated.following : this.user.following }, - showFollow () { - return !this.showApproval && (!this.following || this.updated && !this.updated.following) - } - }, - methods: { - toggleUserExpanded () { - this.userExpanded = !this.userExpanded - }, - approveUser () { - this.$store.state.api.backendInteractor.approveUser(this.user.id) - this.$store.dispatch('removeFollowRequest', this.user) - }, - denyUser () { - this.$store.state.api.backendInteractor.denyUser(this.user.id) - this.$store.dispatch('removeFollowRequest', this.user) - }, - userProfileLink (user) { - return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames) - }, - followUser () { - this.followRequestInProgress = true - requestFollow(this.user, this.$store).then(({ sent, updated }) => { - this.followRequestInProgress = false - this.followRequestSent = sent - this.updated = updated - }) - }, - unfollowUser () { - this.followRequestInProgress = true - requestUnfollow(this.user, this.$store).then(({ updated }) => { - this.followRequestInProgress = false - this.updated = updated - }) - } - } -} - -export default UserCard diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue deleted file mode 100644 index ce4edb3c..00000000 --- a/src/components/user_card/user_card.vue +++ /dev/null @@ -1,159 +0,0 @@ - - - - - diff --git a/src/components/user_card_content/user_card_content.vue b/src/components/user_card_content/user_card_content.vue index a3d24eb1..689b9ec6 100644 --- a/src/components/user_card_content/user_card_content.vue +++ b/src/components/user_card_content/user_card_content.vue @@ -222,6 +222,13 @@ overflow: hidden; flex: 1 1 auto; margin-right: 1em; + + img { + object-fit: contain; + height: 16px; + width: 16px; + vertical-align: middle; + } } .user-screen-name { @@ -386,4 +393,24 @@ } } +.usercard { + width: fill-available; + border-radius: $fallback--panelRadius; + border-radius: var(--panelRadius, $fallback--panelRadius); + border-style: solid; + border-color: $fallback--border; + border-color: var(--border, $fallback--border); + border-width: 1px; + overflow: hidden; + + .panel-heading { + background: transparent; + flex-direction: column; + align-items: stretch; + } + + p { + margin-bottom: 0; + } +} diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index ebf6c61a..7708141c 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -1,8 +1,32 @@ +import { compose } from 'vue-compose' import get from 'lodash/get' import UserCardContent from '../user_card_content/user_card_content.vue' -import UserCard from '../user_card/user_card.vue' +import FollowCard from '../follow_card/follow_card.vue' import Timeline from '../timeline/timeline.vue' -import FollowList from '../follow_list/follow_list.vue' +import withLoadMore from '../../hocs/with_load_more/with_load_more' +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', []), + destory: (props, $store) => $store.dispatch('clearFollowers', props.userId), + childPropName: 'entries', + additionalPropNames: ['userId'] + }), + withList({ getEntryProps: user => ({ user }) }) +)(FollowCard) + +const FriendList = compose( + withLoadMore({ + fetch: (props, $store) => $store.dispatch('addFriends', props.userId), + select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []), + destory: (props, $store) => $store.dispatch('clearFriends', props.userId), + childPropName: 'entries', + additionalPropNames: ['userId'] + }), + withList({ getEntryProps: user => ({ user }) }) +)(FollowCard) const UserProfile = { data () { @@ -121,9 +145,9 @@ const UserProfile = { }, components: { UserCardContent, - UserCard, Timeline, - FollowList + FollowerList, + FriendList } } diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index ba1a7760..a3d2825f 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -18,16 +18,10 @@ :user-id="fetchBy" />
- -
- -
+
- -
- -
+
- +
diff --git a/src/components/who_to_follow/who_to_follow.js b/src/components/who_to_follow/who_to_follow.js index 82098fc2..be0b8827 100644 --- a/src/components/who_to_follow/who_to_follow.js +++ b/src/components/who_to_follow/who_to_follow.js @@ -1,9 +1,9 @@ import apiService from '../../services/api/api.service.js' -import UserCard from '../user_card/user_card.vue' +import FollowCard from '../follow_card/follow_card.vue' const WhoToFollow = { components: { - UserCard + FollowCard }, data () { return { diff --git a/src/components/who_to_follow/who_to_follow.vue b/src/components/who_to_follow/who_to_follow.vue index df2e03c8..1630f5ac 100644 --- a/src/components/who_to_follow/who_to_follow.vue +++ b/src/components/who_to_follow/who_to_follow.vue @@ -4,7 +4,7 @@ {{$t('who_to_follow.who_to_follow')}}
- +
diff --git a/src/hocs/with_load_more/with_load_more.js b/src/hocs/with_load_more/with_load_more.js index e862a39b..74979b87 100644 --- a/src/hocs/with_load_more/with_load_more.js +++ b/src/hocs/with_load_more/with_load_more.js @@ -1,15 +1,17 @@ import Vue from 'vue' -import filter from 'lodash/filter' import isEmpty from 'lodash/isEmpty' +import { getComponentProps } from '../../services/component_utils/component_utils' import './with_load_more.scss' const withLoadMore = ({ fetch, // function to fetch entries and return a promise select, // function to select data from store - childPropName = 'entries' // name of the prop to be passed into the wrapped component + destroy, // function called at "destroyed" lifecycle + childPropName = 'entries', // name of the prop to be passed into the wrapped component + additionalPropNames = [] // additional prop name list of the wrapper component }) => (WrappedComponent) => { - const originalProps = WrappedComponent.props || [] - const props = filter(originalProps, v => v !== 'entries') + const originalProps = Object.keys(getComponentProps(WrappedComponent)) + const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames) return Vue.component('withLoadMore', { render (createElement) { @@ -56,6 +58,7 @@ const withLoadMore = ({ }, destroyed () { window.removeEventListener('scroll', this.scrollLoad) + destroy && destroy(this.$props, this.$store) }, methods: { fetchEntries () { diff --git a/src/hocs/with_subscription/with_subscription.js b/src/hocs/with_subscription/with_subscription.js index 1ac67cba..679409cf 100644 --- a/src/hocs/with_subscription/with_subscription.js +++ b/src/hocs/with_subscription/with_subscription.js @@ -1,16 +1,16 @@ import Vue from 'vue' -import reject from 'lodash/reject' import isEmpty from 'lodash/isEmpty' -import omit from 'lodash/omit' +import { getComponentProps } from '../../services/component_utils/component_utils' import './with_subscription.scss' const withSubscription = ({ fetch, // function to fetch entries and return a promise select, // function to select data from store - childPropName = 'content' // name of the prop to be passed into the wrapped component + childPropName = 'content', // name of the prop to be passed into the wrapped component + additionalPropNames = [] // additional prop name list of the wrapper component }) => (WrappedComponent) => { - const originalProps = WrappedComponent.props || [] - const props = reject(originalProps, v => v === 'content') + const originalProps = Object.keys(getComponentProps(WrappedComponent)) + const props = originalProps.filter(v => v !== childPropName).concat(additionalPropNames) return Vue.component('withSubscription', { props: [ @@ -21,7 +21,7 @@ const withSubscription = ({ if (!this.error && !this.loading) { const props = { props: { - ...omit(this.$props, 'refresh'), + ...this.$props, [childPropName]: this.fetchedData }, on: this.$listeners, diff --git a/src/modules/users.js b/src/modules/users.js index eabfe5ae..093af497 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -72,14 +72,20 @@ export const mutations = { }, // Because frontend doesn't have a reason to keep these stuff in memory // outside of viewing someones user profile. - clearFriendsAndFollowers (state, userKey) { - const user = state.usersObject[userKey] + clearFriends (state, userId) { + const user = state.usersObject[userId] if (!user) { return } user.friends = [] - user.followers = [] user.friendsPage = 0 + }, + clearFollowers (state, userId) { + const user = state.usersObject[userId] + if (!user) { + return + } + user.followers = [] user.followersPage = 0 }, addNewUsers (state, users) { @@ -189,20 +195,19 @@ const users = { }) }, addFollowers ({ rootState, commit }, fetchBy) { - return new Promise((resolve, reject) => { - const user = rootState.users.usersObject[fetchBy] - const page = user.followersPage || 1 - rootState.api.backendInteractor.fetchFollowers({ id: user.id, page }) - .then((followers) => { - commit('addFollowers', { id: user.id, followers, page }) - resolve(followers) - }).catch(() => { - reject() - }) - }) + const user = rootState.users.usersObject[fetchBy] + const page = user.followersPage || 1 + return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page }) + .then((followers) => { + commit('addFollowers', { id: user.id, followers, page }) + return followers + }) }, - clearFriendsAndFollowers ({ commit }, userKey) { - commit('clearFriendsAndFollowers', userKey) + clearFriends ({ commit }, userId) { + commit('clearFriends', userId) + }, + clearFollowers ({ commit }, userId) { + commit('clearFollowers', userId) }, registerPushNotifications (store) { const token = store.state.currentUser.credentials diff --git a/src/services/component_utils/component_utils.js b/src/services/component_utils/component_utils.js new file mode 100644 index 00000000..77ea14a1 --- /dev/null +++ b/src/services/component_utils/component_utils.js @@ -0,0 +1,10 @@ +import isFunction from 'lodash/isFunction' + +const getComponentOptions = (Component) => (isFunction(Component)) ? Component.options : Component + +const getComponentProps = (Component) => getComponentOptions(Component).props + +export { + getComponentOptions, + getComponentProps +}