Compare commits

...

4 Commits

9 changed files with 108 additions and 58 deletions

View File

@ -23,6 +23,7 @@ const Timeline = {
'userId', 'userId',
'listId', 'listId',
'tag', 'tag',
'fetchParams',
'embedded', 'embedded',
'count', 'count',
'pinnedStatusIds', 'pinnedStatusIds',
@ -181,7 +182,8 @@ const Timeline = {
showImmediately: true, showImmediately: true,
userId: this.userId, userId: this.userId,
listId: this.listId, listId: this.listId,
tag: this.tag tag: this.tag,
params: this.fetchParams,
}).then(({ statuses }) => { }).then(({ statuses }) => {
if (statuses && statuses.length === 0) { if (statuses && statuses.length === 0) {
this.bottomedOut = true this.bottomedOut = true

View File

@ -3,6 +3,7 @@ import UserCard from '../user_card/user_card.vue'
import FollowCard from '../follow_card/follow_card.vue' import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue' import Timeline from '../timeline/timeline.vue'
import Conversation from '../conversation/conversation.vue' import Conversation from '../conversation/conversation.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import RichContent from 'src/components/rich_content/rich_content.jsx' import RichContent from 'src/components/rich_content/rich_content.jsx'
import List from '../list/list.vue' import List from '../list/list.vue'
@ -55,7 +56,12 @@ const UserProfile = {
followsTab: 'users', followsTab: 'users',
footerRef: null, footerRef: null,
note: null, note: null,
noteLoading: false noteLoading: false,
showPinned: true,
filterParams: {
showReplies: false,
showRepeats: true,
}
} }
}, },
created () { created () {
@ -70,10 +76,35 @@ const UserProfile = {
}, },
computed: { computed: {
timeline () { timeline () {
return this.$store.state.statuses.timelines.user const { user, pinned } = this.$store.state.statuses.timelines
if (!this.showPinned) {
return user
}
return {
...user,
statuses: [...pinned.statuses, ...user.statuses],
visibleStatuses: [...pinned.visibleStatuses, ...user.visibleStatuses],
statusesObject: {
...pinned.statusesObject,
...user.statusesObject,
}
}
}, },
replies () { hasPinned () {
return this.$store.state.statuses.timelines.replies return !!(this.user.pinnedStatusIds || []).length
},
fetchParams () {
if (this.tab !== 'statuses') {
return []
}
const params = []
if (!this.filterParams.showReplies) {
params.push(['exclude_replies', 1])
}
if (!this.filterParams.showRepeats) {
params.push(['exclude_reblogs', 1])
}
return params
}, },
favorites () { favorites () {
return this.$store.state.statuses.timelines.favorites return this.$store.state.statuses.timelines.favorites
@ -118,16 +149,17 @@ const UserProfile = {
if (this.isUs) timelineTabMap['favorites'] = 'favorites' if (this.isUs) timelineTabMap['favorites'] = 'favorites'
const timeline = timelineTabMap[nextTab] const timeline = timelineTabMap[nextTab]
const fetchArgs = { timeline: timeline, userId: this.userId, params: this.fetchParams }
if (timeline) { if (timeline) {
this.stopFetching() this.stopFetching()
this.$store.dispatch('startFetchingTimeline', { timeline: timeline, userId: this.userId }) this.$store.dispatch('startFetchingTimeline', fetchArgs)
} }
}, },
load (userNameOrId) { load (userNameOrId) {
const loadById = (userId) => { const loadById = (userId) => {
this.userId = userId this.userId = userId
const timelines = ['user', 'favorites', 'replies', 'media'] const timelines = ['pinned', 'user', 'favorites', 'media']
timelines.forEach((timeline) => { timelines.forEach((timeline) => {
this.$store.commit('clearTimeline', { timeline: timeline }) this.$store.commit('clearTimeline', { timeline: timeline })
}) })
@ -165,7 +197,6 @@ const UserProfile = {
}, },
stopFetching () { stopFetching () {
this.$store.dispatch('stopFetchingTimeline', 'user') this.$store.dispatch('stopFetchingTimeline', 'user')
this.$store.dispatch('stopFetchingTimeline', 'replies')
this.$store.dispatch('stopFetchingTimeline', 'favorites') this.$store.dispatch('stopFetchingTimeline', 'favorites')
this.$store.dispatch('stopFetchingTimeline', 'media') this.$store.dispatch('stopFetchingTimeline', 'media')
}, },
@ -198,6 +229,15 @@ const UserProfile = {
}, 1500) }, 1500)
}, },
watch: { watch: {
filterParams: {
handler () {
// Clear the user timeline if previously fetched posts may not be relevant anymore
this.$store.dispatch('stopFetchingTimeline', 'user')
this.$store.commit('clearTimeline', { timeline: 'user', excludeUserId: true })
this.onRouteChange(null, this.tab)
},
deep: true
},
'$route.params.id': function (newVal) { '$route.params.id': function (newVal) {
if (isUserPage(this.$route) && newVal) { if (isUserPage(this.$route) && newVal) {
this.switchUser(newVal) this.switchUser(newVal)
@ -215,16 +255,17 @@ const UserProfile = {
} }
}, },
components: { components: {
Checkbox,
Conversation,
FollowCard,
FollowedTagCard, FollowedTagCard,
UserCard, FollowedTagList,
Timeline,
FollowerList, FollowerList,
FriendList, FriendList,
FollowCard,
TabSwitcher,
Conversation,
RichContent, RichContent,
FollowedTagList TabSwitcher,
Timeline,
UserCard
} }
} }

View File

@ -75,31 +75,27 @@
:render-only-focused="true" :render-only-focused="true"
:on-switch="onTabSwitch" :on-switch="onTabSwitch"
> >
<Timeline <div key="statuses" :label="$t('user_card.statuses')">
key="statuses" <div class="user-post-filters">
:label="$t('user_card.statuses')" <Checkbox v-model="filterParams.showReplies">{{ $t('user_card.with_replies') }}</Checkbox>
:count="user.statuses_count" <Checkbox v-model="filterParams.showRepeats">{{ $t('user_card.with_repeats') }}</Checkbox>
:embedded="true" <Checkbox :disabled="!hasPinned" v-model="showPinned">{{ $t('user_card.with_pinned') }}</Checkbox>
:title="$t('user_profile.timeline_title')" </div>
:timeline="timeline" <Timeline
timeline-name="user" key="statuses"
:user-id="userId" :label="$t('user_card.statuses')"
:pinned-status-ids="user.pinnedStatusIds" :count="user.statuses_count"
:in-profile="true" :embedded="true"
:footer-slipgate="footerRef" :title="$t('user_profile.timeline_title')"
/> :timeline="timeline"
<Timeline timeline-name="user"
key="replies" :user-id="userId"
:label="$t('user_card.replies')" :pinned-status-ids="user.pinnedStatusIds"
:count="user.statuses_count" :in-profile="true"
:embedded="true" :fetch-params="fetchParams"
:title="$t('user_card.replies')" :footer-slipgate="footerRef"
:timeline="replies" />
timeline-name="replies" </div>
:user-id="userId"
:in-profile="true"
:footer-slipgate="footerRef"
/>
<div <div
v-if="followsTabVisible" v-if="followsTabVisible"
key="followees" key="followees"
@ -268,6 +264,14 @@
} }
} }
.user-post-filters {
display: flex;
flex-direction: row;
grid-gap: 1em;
justify-content: center;
padding: 0.75em;
}
.userlist-placeholder { .userlist-placeholder {
display: flex; display: flex;
justify-content: center; justify-content: center;

View File

@ -1179,10 +1179,13 @@
"per_day": "per day", "per_day": "per day",
"remote_follow": "Remote follow", "remote_follow": "Remote follow",
"remove_follower": "Remove follower", "remove_follower": "Remove follower",
"replies": "With Replies", "with_pinned": "With Pinned",
"with_replies": "With Replies",
"with_repeats": "With Repeats",
"report": "Report", "report": "Report",
"requested_by": "Has requested to follow you", "requested_by": "Has requested to follow you",
"show_repeats": "Show repeats", "show_repeats": "Show repeats",
"pinned": "Pinned",
"statuses": "Posts", "statuses": "Posts",
"subscribe": "Subscribe", "subscribe": "Subscribe",
"unblock": "Unblock", "unblock": "Unblock",

View File

@ -208,12 +208,13 @@ const api = {
timeline = 'friends', timeline = 'friends',
tag = false, tag = false,
userId = false, userId = false,
listId = false listId = false,
params
}) { }) {
if (store.state.fetchers[timeline]) return if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({ const fetcher = store.state.backendInteractor.startFetchingTimeline({
timeline, store, userId, listId, tag timeline, store, userId, listId, tag, params
}) })
store.commit('addFetcher', { fetcherName: timeline, fetcher }) store.commit('addFetcher', { fetcherName: timeline, fetcher })
}, },

View File

@ -65,7 +65,7 @@ export const defaultState = () => ({
bookmarks: emptyTl(), bookmarks: emptyTl(),
list: emptyTl(), list: emptyTl(),
bubble: emptyTl(), bubble: emptyTl(),
replies: emptyTl() pinned: emptyTl(),
} }
}) })
@ -184,7 +184,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
// This makes sure that user timeline won't get data meant for other // This makes sure that user timeline won't get data meant for other
// user. I.e. opening different user profiles makes request which could // user. I.e. opening different user profiles makes request which could
// return data late after user already viewing different user profile // return data late after user already viewing different user profile
if ((timeline === 'user' || timeline === 'media' || timeline === 'replies') && timelineObject.userId !== userId) { if ((timeline === 'user' || timeline === 'media') && timelineObject.userId !== userId) {
return return
} }
@ -641,7 +641,7 @@ const statuses = {
}, },
fetchPinnedStatuses ({ rootState, dispatch }, userId) { fetchPinnedStatuses ({ rootState, dispatch }, userId) {
rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId }) rootState.api.backendInteractor.fetchPinnedStatuses({ id: userId })
.then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'user', userId, showImmediately: true, noIdUpdate: true })) .then(statuses => dispatch('addNewStatuses', { statuses, timeline: 'pinned', userId, showImmediately: true, noIdUpdate: true }))
}, },
pinStatus ({ rootState, dispatch }, statusId) { pinStatus ({ rootState, dispatch }, statusId) {
return rootState.api.backendInteractor.pinOwnStatus({ id: statusId }) return rootState.api.backendInteractor.pinOwnStatus({ id: statusId })

View File

@ -695,7 +695,8 @@ const fetchTimeline = ({
listId = false, listId = false,
tag = false, tag = false,
withMuted = false, withMuted = false,
replyVisibility = 'all' replyVisibility = 'all',
params: addtlParams,
}) => { }) => {
const timelineUrls = { const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE, public: MASTODON_PUBLIC_TIMELINE,
@ -713,11 +714,11 @@ const fetchTimeline = ({
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL bookmarks: MASTODON_BOOKMARK_TIMELINE_URL
} }
const isNotifications = timeline === 'notifications' const isNotifications = timeline === 'notifications'
const params = []
let url = timelineUrls[timeline] let url = timelineUrls[timeline]
const params = addtlParams ? [...addtlParams] : []
if (timeline === 'user' || timeline === 'media' || timeline === 'replies') { if (timeline === 'user' || timeline === 'media') {
url = url(userId) url = url(userId)
} }
@ -749,9 +750,6 @@ const fetchTimeline = ({
if (replyVisibility !== 'all') { if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility]) params.push(['reply_visibility', replyVisibility])
} }
if (timeline === 'user') {
params.push(['exclude_replies', 1])
}
params.push(['limit', 20]) params.push(['limit', 20])

View File

@ -7,8 +7,8 @@ import configFetcher from '../config_fetcher/config_fetcher.service.js'
import reportsFetcher from '../reports_fetcher/reports_fetcher.service.js' import reportsFetcher from '../reports_fetcher/reports_fetcher.service.js'
const backendInteractorService = credentials => ({ const backendInteractorService = credentials => ({
startFetchingTimeline ({ timeline, store, userId = false, listId = false, tag }) { startFetchingTimeline ({ timeline, store, userId = false, listId = false, tag, params }) {
return timelineFetcher.startFetching({ timeline, store, credentials, userId, listId, tag }) return timelineFetcher.startFetching({ timeline, store, credentials, userId, listId, tag, params })
}, },
fetchTimeline (args) { fetchTimeline (args) {

View File

@ -26,9 +26,10 @@ const fetchAndUpdate = ({
listId = false, listId = false,
tag = false, tag = false,
until, until,
since since,
params
}) => { }) => {
const args = { timeline, credentials } const args = { timeline, credentials, params }
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const { getters } = store const { getters } = store
const timelineData = rootState.statuses.timelines[camelCase(timeline)] const timelineData = rootState.statuses.timelines[camelCase(timeline)]
@ -78,15 +79,15 @@ const fetchAndUpdate = ({
}) })
} }
const startFetching = ({ timeline = 'friends', credentials, store, userId = false, listId = false, tag = false }) => { const startFetching = ({ timeline = 'friends', credentials, store, userId = false, listId = false, tag = false, params }) => {
const rootState = store.rootState || store.state const rootState = store.rootState || store.state
const timelineData = rootState.statuses.timelines[camelCase(timeline)] const timelineData = rootState.statuses.timelines[camelCase(timeline)]
const showImmediately = timelineData.visibleStatuses.length === 0 const showImmediately = timelineData.visibleStatuses.length === 0
timelineData.userId = userId timelineData.userId = userId
timelineData.listId = listId timelineData.listId = listId
fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, listId, tag }) fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, listId, tag, params })
const boundFetchAndUpdate = () => const boundFetchAndUpdate = () =>
fetchAndUpdate({ timeline, credentials, store, userId, listId, tag }) fetchAndUpdate({ timeline, credentials, store, userId, listId, tag, params })
return promiseInterval(boundFetchAndUpdate, 20000) return promiseInterval(boundFetchAndUpdate, 20000)
} }
const timelineFetcher = { const timelineFetcher = {