timeline fetcher improvements
This commit is contained in:
parent
e1e20b5ad2
commit
d7f19d75f1
8 changed files with 135 additions and 157 deletions
|
|
@ -322,15 +322,15 @@ const Status = {
|
||||||
shouldNotMute() {
|
shouldNotMute() {
|
||||||
if (this.ignoreMute) return true
|
if (this.ignoreMute) return true
|
||||||
if (this.focused) return true
|
if (this.focused) return true
|
||||||
const { reblog } = this.status
|
const { reblog } = this.mainStatus
|
||||||
return (
|
return (
|
||||||
((this.inProfile &&
|
((this.inProfile &&
|
||||||
// Don't mute user's posts on user timeline (except reblogs)
|
// Don't mute user's posts on user timeline (except reblogs)
|
||||||
((!reblog && status.user.id === this.profileUserId) ||
|
((!reblog && this.mainStatus.user.id === this.profileUserId) ||
|
||||||
// Same as above but also allow self-reblogs
|
// Same as above but also allow self-reblogs
|
||||||
reblog?.user.id === this.profileUserId)) ||
|
reblog?.user.id === this.profileUserId)) ||
|
||||||
// Don't mute statuses in muted conversation when said conversation is opened
|
// Don't mute statuses in muted conversation when said conversation is opened
|
||||||
(this.inConversation && status.thread_muted)) &&
|
(this.inConversation && this.mainStatus.thread_muted)) &&
|
||||||
// No excuses if post has muted words
|
// No excuses if post has muted words
|
||||||
!this.muteFilterHits.length > 0
|
!this.muteFilterHits.length > 0
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ const Timeline = {
|
||||||
showScrollTop: false,
|
showScrollTop: false,
|
||||||
paused: false,
|
paused: false,
|
||||||
unfocused: false,
|
unfocused: false,
|
||||||
bottomedOut: false,
|
|
||||||
virtualScrollIndex: 0,
|
virtualScrollIndex: 0,
|
||||||
blockingClicks: false,
|
blockingClicks: false,
|
||||||
}
|
}
|
||||||
|
|
@ -183,16 +182,7 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
fetchOlderStatuses: throttle(
|
fetchOlderStatuses: throttle(
|
||||||
function () {
|
function () {
|
||||||
this.timeline.fetcher
|
this.timeline.fetcher.fetchOlder()
|
||||||
.fetchAndUpdate({
|
|
||||||
older: true,
|
|
||||||
showImmediately: true,
|
|
||||||
})
|
|
||||||
.then(({ statuses }) => {
|
|
||||||
if (statuses?.length === 0) {
|
|
||||||
this.bottomedOut = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
this,
|
this,
|
||||||
|
|
@ -222,7 +212,7 @@ const Timeline = {
|
||||||
let err = statuses[approxIndex].getBoundingClientRect().y
|
let err = statuses[approxIndex].getBoundingClientRect().y
|
||||||
|
|
||||||
// if we have a previous scroll index that can be used, test if it's
|
// if we have a previous scroll index that can be used, test if it's
|
||||||
// closer than the previous approximation, use it if so
|
|
||||||
|
|
||||||
const virtualScrollIndexY =
|
const virtualScrollIndexY =
|
||||||
statuses[cappedScrollIndex].getBoundingClientRect().y
|
statuses[cappedScrollIndex].getBoundingClientRect().y
|
||||||
|
|
@ -250,7 +240,7 @@ const Timeline = {
|
||||||
const bodyBRect = document.body.getBoundingClientRect()
|
const bodyBRect = document.body.getBoundingClientRect()
|
||||||
const height = Math.max(bodyBRect.height, -bodyBRect.y)
|
const height = Math.max(bodyBRect.height, -bodyBRect.y)
|
||||||
if (
|
if (
|
||||||
this.timeline.loading === false &&
|
!this.timeline.fetcher.loading.value &&
|
||||||
this.$el.offsetHeight > 0 &&
|
this.$el.offsetHeight > 0 &&
|
||||||
window.innerHeight + window.pageYOffset >= height - 750
|
window.innerHeight + window.pageYOffset >= height - 750
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -90,19 +90,19 @@
|
||||||
:disabled="!embedded || !footerSlipgate"
|
:disabled="!embedded || !footerSlipgate"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="count===0"
|
v-if="count === 0"
|
||||||
class="new-status-notification text-center faint"
|
class="new-status-notification text-center faint"
|
||||||
>
|
>
|
||||||
{{ $t('timeline.no_statuses') }}
|
{{ $t('timeline.no_statuses') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="bottomedOut"
|
v-else-if="timeline.fetcher.bottomedOut"
|
||||||
class="new-status-notification text-center faint"
|
class="new-status-notification text-center faint"
|
||||||
>
|
>
|
||||||
{{ $t('timeline.no_more_statuses') }}
|
{{ $t('timeline.no_more_statuses') }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else-if="!timeline.loading"
|
v-else-if="!timeline.fetcher.loading"
|
||||||
class="button-unstyled -link"
|
class="button-unstyled -link"
|
||||||
@click.prevent="fetchOlderStatuses()"
|
@click.prevent="fetchOlderStatuses()"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
import { promiseInterval } from '../promise_interval/promise_interval.js'
|
|
||||||
|
|
||||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
|
||||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
|
||||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
|
||||||
import { ARGUMENT_MAP, useTimelinesStore } from 'src/stores/timelines.js'
|
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
|
||||||
|
|
||||||
import { fetchTimeline } from 'src/api/timelines.js'
|
|
||||||
|
|
||||||
const REPLY_VISIBILITY_TIMELINES = new Set([
|
|
||||||
'friends',
|
|
||||||
'public',
|
|
||||||
'publicAndExternal',
|
|
||||||
'bubble',
|
|
||||||
])
|
|
||||||
|
|
||||||
const fetchAndUpdate = (
|
|
||||||
{ timeline, argument, credentials },
|
|
||||||
{ older = false, showImmediately = false },
|
|
||||||
) => {
|
|
||||||
timeline.loading = true
|
|
||||||
const { hideMutedPosts, replyVisibility } =
|
|
||||||
useMergedConfigStore().mergedConfig
|
|
||||||
const loggedIn = useUsersStore().loggedIn
|
|
||||||
|
|
||||||
const args = { timeline: timeline.name, credentials }
|
|
||||||
const mainArg = ARGUMENT_MAP[timeline.name]
|
|
||||||
if (mainArg) args[mainArg] = argument
|
|
||||||
|
|
||||||
if (older) {
|
|
||||||
// When minId = 0 we need to fetch without maxId param
|
|
||||||
args.maxId = timeline.minId || null
|
|
||||||
} else {
|
|
||||||
args.sinceId = timeline.maxId || null
|
|
||||||
}
|
|
||||||
|
|
||||||
args.withMuted = !hideMutedPosts
|
|
||||||
if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline)) {
|
|
||||||
args.replyVisibility = replyVisibility
|
|
||||||
}
|
|
||||||
|
|
||||||
const numStatusesBeforeFetch = timeline.statusIds.size
|
|
||||||
|
|
||||||
return fetchTimeline(args)
|
|
||||||
.then((response) => {
|
|
||||||
const { data: statuses, pagination, timestamp } = response
|
|
||||||
if (
|
|
||||||
!older &&
|
|
||||||
statuses.length >= 20 &&
|
|
||||||
!timeline.loading &&
|
|
||||||
numStatusesBeforeFetch > 0
|
|
||||||
) {
|
|
||||||
useTimelinesStore().queueFlush(timeline.name, timeline.maxId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const processed = useStatusesStore()
|
|
||||||
.addNewStatuses({ statuses, timestamp })
|
|
||||||
.filter(Boolean)
|
|
||||||
.map(({ id }) => id)
|
|
||||||
|
|
||||||
useTimelinesStore().addStatusesToTimeline(timeline.name, argument, {
|
|
||||||
statuses: processed,
|
|
||||||
showImmediately,
|
|
||||||
older,
|
|
||||||
pagination,
|
|
||||||
})
|
|
||||||
return { statuses, pagination }
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
if (error.statusCode === 403 && timeline === 'favorites') {
|
|
||||||
useInstanceCapabilitiesStore().pleromaPublicFavouritesAvailable = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.error('Timeline Error', error)
|
|
||||||
useInterfaceStore().pushGlobalNotice({
|
|
||||||
level: 'error',
|
|
||||||
messageKey: 'timeline.error',
|
|
||||||
messageArgs: [error.message],
|
|
||||||
timeout: 5000,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
timeline.loading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const timelineFetcher = (timeline, argument, credentials) => {
|
|
||||||
const state = {
|
|
||||||
interval: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
const boundFetchAndUpdate = ({
|
|
||||||
showImmediately,
|
|
||||||
older,
|
|
||||||
} = {}) =>
|
|
||||||
fetchAndUpdate(
|
|
||||||
{
|
|
||||||
timeline,
|
|
||||||
argument,
|
|
||||||
credentials,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
older,
|
|
||||||
showImmediately,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
const startFetching = () => {
|
|
||||||
if (state.interval) throw new Error('Interval already exists!')
|
|
||||||
|
|
||||||
boundFetchAndUpdate({
|
|
||||||
showImmediately: timeline.visibleStatusIds.size === 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
state.interval = promiseInterval(boundFetchAndUpdate, 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopFetching = () => {
|
|
||||||
state.interval.stop()
|
|
||||||
state.interval = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
startFetching,
|
|
||||||
stopFetching,
|
|
||||||
fetchAndUpdate: boundFetchAndUpdate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default timelineFetcher
|
|
||||||
121
src/stores/fetchers/timeline_fetcher.js
Normal file
121
src/stores/fetchers/timeline_fetcher.js
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
|
||||||
|
|
||||||
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
|
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||||
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
|
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
|
import { ARGUMENT_MAP, useTimelinesStore } from 'src/stores/timelines.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
|
import { fetchTimeline } from 'src/api/timelines.js'
|
||||||
|
|
||||||
|
const REPLY_VISIBILITY_TIMELINES = new Set([
|
||||||
|
'friends',
|
||||||
|
'public',
|
||||||
|
'publicAndExternal',
|
||||||
|
'bubble',
|
||||||
|
])
|
||||||
|
|
||||||
|
const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
|
const loading = ref(false)
|
||||||
|
const bottomedOut = ref(false)
|
||||||
|
const interval = ref(null)
|
||||||
|
|
||||||
|
const fetchAndUpdate = ({ older = false, showImmediately = false } = {}) => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
const { hideMutedPosts, replyVisibility } =
|
||||||
|
useMergedConfigStore().mergedConfig
|
||||||
|
const loggedIn = useUsersStore().loggedIn
|
||||||
|
|
||||||
|
const args = { timeline: timeline.name, credentials }
|
||||||
|
const mainArg = ARGUMENT_MAP[timeline.name]
|
||||||
|
if (mainArg) args[mainArg] = argument
|
||||||
|
|
||||||
|
if (older) {
|
||||||
|
// When minId = 0 we need to fetch without maxId param
|
||||||
|
args.maxId = timeline.minId || null
|
||||||
|
} else {
|
||||||
|
args.sinceId = timeline.maxId || null
|
||||||
|
}
|
||||||
|
|
||||||
|
args.withMuted = !hideMutedPosts
|
||||||
|
if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline)) {
|
||||||
|
args.replyVisibility = replyVisibility
|
||||||
|
}
|
||||||
|
|
||||||
|
const numStatusesBeforeFetch = timeline.statusIds.size
|
||||||
|
|
||||||
|
return fetchTimeline(args)
|
||||||
|
.then(({ data: statuses, pagination, timestamp }) => {
|
||||||
|
if (
|
||||||
|
!older &&
|
||||||
|
!loading.value &&
|
||||||
|
statuses.length >= 20 &&
|
||||||
|
numStatusesBeforeFetch > 0
|
||||||
|
) {
|
||||||
|
useTimelinesStore().queueFlush(timeline.name, timeline.maxId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (older && statuses.length === 0) {
|
||||||
|
bottomedOut.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const processed = useStatusesStore()
|
||||||
|
.addNewStatuses({ statuses, timestamp })
|
||||||
|
.map(({ id }) => id)
|
||||||
|
|
||||||
|
useTimelinesStore().addStatusesToTimeline(timeline.name, argument, {
|
||||||
|
statuses: processed,
|
||||||
|
showImmediately,
|
||||||
|
older,
|
||||||
|
pagination,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { statuses, pagination }
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.statusCode === 403 && timeline === 'favorites') {
|
||||||
|
useInstanceCapabilitiesStore().pleromaPublicFavouritesAvailable = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.error('Timeline Error', error)
|
||||||
|
useInterfaceStore().pushGlobalNotice({
|
||||||
|
level: 'error',
|
||||||
|
messageKey: 'timeline.error',
|
||||||
|
messageArgs: [error.message],
|
||||||
|
timeout: 5000,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const startFetching = () => {
|
||||||
|
if (interval.value) throw new Error('Interval already exists!')
|
||||||
|
|
||||||
|
fetchAndUpdate({
|
||||||
|
showImmediately: timeline.visibleStatusIds.size === 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
interval.value = promiseInterval(fetchAndUpdate, 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopFetching = () => {
|
||||||
|
interval.value.stop()
|
||||||
|
interval.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
startFetching,
|
||||||
|
stopFetching,
|
||||||
|
fetchOlder: () => fetchAndUpdate({ showImmediately: true, older: true }),
|
||||||
|
fetchNewer: () => fetchAndUpdate({ showImmediately: true, older: false }),
|
||||||
|
loading,
|
||||||
|
bottomedOut,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default timelineFetcher
|
||||||
|
|
@ -7,7 +7,7 @@ import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
import { TIMELINE_STREAM_MAP, useStreamingStore } from 'src/stores/streaming.js'
|
import { TIMELINE_STREAM_MAP, useStreamingStore } from 'src/stores/streaming.js'
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js'
|
import timelineFetcher from 'src/stores/fetchers/timeline_fetcher.js'
|
||||||
|
|
||||||
const emptyTl = (name, argument = null) => {
|
const emptyTl = (name, argument = null) => {
|
||||||
const result = {
|
const result = {
|
||||||
|
|
@ -18,7 +18,6 @@ const emptyTl = (name, argument = null) => {
|
||||||
newStatusCount: 0,
|
newStatusCount: 0,
|
||||||
maxId: '',
|
maxId: '',
|
||||||
minId: '',
|
minId: '',
|
||||||
loading: false,
|
|
||||||
streaming: false,
|
streaming: false,
|
||||||
flushMarker: 0,
|
flushMarker: 0,
|
||||||
fetcher: null,
|
fetcher: null,
|
||||||
|
|
|
||||||
|
|
@ -559,8 +559,8 @@ export const useUsersStore = defineStore('users', {
|
||||||
return blockUser({ id, expiresIn }).then((result) => {
|
return blockUser({ id, expiresIn }).then((result) => {
|
||||||
this.updateUserRelationships(result)
|
this.updateUserRelationships(result)
|
||||||
|
|
||||||
useStatusesStore().wipeUserStatuses(id)
|
const ids = useStatusesStore().wipeUserStatuses(id)
|
||||||
useTimelinesStore().wipeUserStatuses(id)
|
useTimelinesStore().wipeStatuses(ids)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
blockUsers(data = []) {
|
blockUsers(data = []) {
|
||||||
|
|
|
||||||
|
|
@ -1119,7 +1119,7 @@ describe('Users store', () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
vi.spyOn(useTimelinesStore(), 'wipeUserStatuses').mockImplementation(
|
vi.spyOn(useTimelinesStore(), 'wipeStatuses').mockImplementation(
|
||||||
async () => {
|
async () => {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
},
|
},
|
||||||
|
|
@ -1150,7 +1150,7 @@ describe('Users store', () => {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
vi.spyOn(useTimelinesStore(), 'wipeUserStatuses').mockImplementation(
|
vi.spyOn(useTimelinesStore(), 'wipeStatuses').mockImplementation(
|
||||||
async () => {
|
async () => {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue