id))
+ }
+
const amount = this.timeline.visibleStatusIds.size
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
index ea0d9a884..185a127c4 100644
--- a/src/components/timeline_menu/timeline_menu.js
+++ b/src/components/timeline_menu/timeline_menu.js
@@ -68,7 +68,6 @@ const TimelineMenu = {
return '#' + this.$route.params.tag
}
if (route === 'lists-timeline') {
- console.log(useListsStore, this.$route.params.id)
return useListsStore().findListTitle(this.$route.params.id)
}
if (route === 'bookmark-folder') {
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index 5304608b8..739c1579b 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -238,7 +238,7 @@ export default {
return useUsersStore().relationship(this.userId)
},
isOtherUser() {
- return this.user.id !== useUsersStore().currentUser.id
+ return this.user.id !== useUsersStore().currentUser?.id
},
subscribeUrl() {
const serverUrl = new URL(this.user.statusnet_profile_url)
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
index aa530e3f4..f5a403be2 100644
--- a/src/components/user_list_popover/user_list_popover.js
+++ b/src/components/user_list_popover/user_list_popover.js
@@ -24,10 +24,13 @@ const UserListPopover = {
UserAvatar,
},
computed: {
- usersCapped() {
+ users() {
return [...this.userIds]
- .slice(0, 16)
.map((id) => useUsersStore().findUser(id))
+ .filter(Boolean)
+ },
+ usersCapped() {
+ return [...this.users].slice(0, 16)
},
allowNonSquareEmoji() {
return useMergedConfigStore().mergedConfig.nonSquareEmoji
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 8c7379742..0e861fe77 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -1634,7 +1634,6 @@
"no_statuses": "No statuses",
"socket_reconnected": "Realtime connection established",
"socket_disconnected": "Realtime connection unavaialable",
- "socket_closed": "Realtime connection closed",
"socket_broke": "Realtime connection lost: CloseEvent code {0}",
"quick_view_settings": "Quick view settings",
"quick_filter_settings": "Quick filter settings",
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 9add0b702..88e2fd0a5 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -385,10 +385,13 @@ export const parseLinkHeaderPagination = (linkHeader, opts = {}) => {
const maxId = parsedLinkHeader.next?.max_id
const minId = parsedLinkHeader.prev?.min_id
- return {
- maxId: flakeId ? maxId : Number.parseInt(maxId, 10),
- minId: flakeId ? minId : Number.parseInt(minId, 10),
- }
+ const result = {}
+ if (maxId !== undefined)
+ result.maxId = flakeId ? maxId : Number.parseInt(maxId, 10)
+ if (minId !== undefined)
+ result.minId = flakeId ? minId : Number.parseInt(minId, 10)
+
+ return result
}
export const parseChat = (chat) => {
diff --git a/src/stores/fetchers/notifications_fetcher.js b/src/stores/fetchers/notifications_fetcher.js
index a31877072..5403d59c9 100644
--- a/src/stores/fetchers/notifications_fetcher.js
+++ b/src/stores/fetchers/notifications_fetcher.js
@@ -36,7 +36,7 @@ const notificationsFetcher = (credentials) => {
const notifications = response.data
if (older && notifications.length === 0) bottomedOut.value = true
- useNotificationsStore().addNewNotifications(response)
+ useNotificationsStore().addNewNotifications(response, older)
} catch (error) {
if (
error.statusCode === 400 &&
@@ -78,16 +78,13 @@ const notificationsFetcher = (credentials) => {
args.timeline = 'notifications'
if (older) {
- if (timelineData.minId !== Number.POSITIVE_INFINITY) {
+ if (timelineData.minId !== '') {
args.maxId = timelineData.minId
}
return await fetchNotifications({ args, older })
} else {
// fetch new notifications
- if (
- sinceId === undefined &&
- timelineData.maxId !== Number.POSITIVE_INFINITY
- ) {
+ if (sinceId === undefined && timelineData.maxId !== '') {
args.sinceId = timelineData.maxId
} else if (sinceId !== null) {
args.sinceId = sinceId
diff --git a/src/stores/fetchers/timeline_fetcher.js b/src/stores/fetchers/timeline_fetcher.js
index 494377b94..7fae0800d 100644
--- a/src/stores/fetchers/timeline_fetcher.js
+++ b/src/stores/fetchers/timeline_fetcher.js
@@ -52,7 +52,11 @@ const timelineFetcher = (timeline, argument, credentials) => {
const numStatusesBeforeFetch = timeline.statusIds.size
- if (older && bottomedOut.value) return
+ if (older && bottomedOut.value) {
+ loadingOlder.value = false
+ return
+ }
+
return fetchTimeline(args)
.then(({ data, pagination, timestamp }) => {
// No statuses for timeline, ever.
@@ -135,6 +139,9 @@ const timelineFetcher = (timeline, argument, credentials) => {
loadingOlder,
loadingNewer,
bottomedOut,
+ resetBottomedOut: () => {
+ bottomedOut.value = false
+ },
}
}
diff --git a/src/stores/interface.js b/src/stores/interface.js
index e87a0ecd9..f2b2d86c7 100644
--- a/src/stores/interface.js
+++ b/src/stores/interface.js
@@ -134,14 +134,7 @@ export const useInterfaceStore = defineStore('interface', {
1001, // Going away
])
const { code } = closeEvent.original
- if (intendedCodes.has(code)) {
- this.pushGlobalNotice({
- level: 'success',
- messageKey: 'timeline.socket_closed',
- messageArgs: [code],
- timeout: 5000,
- })
- } else {
+ if (!intendedCodes.has(code)) {
this.pushGlobalNotice({
level: 'error',
messageKey: 'timeline.socket_broke',
diff --git a/src/stores/streaming.js b/src/stores/streaming.js
index aac1fa860..dae047f46 100644
--- a/src/stores/streaming.js
+++ b/src/stores/streaming.js
@@ -108,6 +108,8 @@ export const useStreamingStore = defineStore('streaming', {
}
},
initSocket(initial) {
+ if (this.socket) throw new Error('Socket already exists!')
+
this.state = initial
? WSConnectionStatus.STARTING_INITIAL
: WSConnectionStatus.STARTING
@@ -129,7 +131,11 @@ export const useStreamingStore = defineStore('streaming', {
},
stopSocket() {
this.socket.close()
+ this.socket = null
this.state = WSConnectionStatus.CLOSED
+ this.retrying = false
+ this.retryMultiplier = 1
+ this.error = null
},
getSubArgs(stream) {
@@ -229,6 +235,8 @@ export const useStreamingStore = defineStore('streaming', {
)
setTimeout(() => {
+ if (this.retrying) return // retry aborted (i.e. due to logout)
+
this.initSocket()
}, retryTimeout(this.retryMultiplier))
diff --git a/src/stores/timelines.js b/src/stores/timelines.js
index 6dda2f23b..86a2c617e 100644
--- a/src/stores/timelines.js
+++ b/src/stores/timelines.js
@@ -81,6 +81,7 @@ export const ARGUMENT_MAP = {
user: 'userId',
userPinned: 'userId',
media: 'userId',
+ favorites: 'userId',
}
const TIMELINES = new Set([
@@ -180,7 +181,7 @@ export const useTimelinesStore = defineStore('timelines', {
timeline.socket.handlers
timeline.socket.et.removeEventListener('open', openHandler)
timeline.socket.et.removeEventListener('close', closeHandler)
- timeline.socket.et.removeEventListener('message', messageHandler)
+ timeline.socket.et.removeEventListener('update', messageHandler)
}
this[timelineName] = emptyTl(timelineName)
@@ -196,6 +197,7 @@ export const useTimelinesStore = defineStore('timelines', {
timeline.maxId = ''
timeline.minId = ''
timeline.reloadNeeded = false
+ timeline.fetcher.resetBottomedOut()
},
activatePersistents() {
TIMELINES.forEach((name) => {
@@ -266,8 +268,6 @@ export const useTimelinesStore = defineStore('timelines', {
if (statuses.length === 0) return
const timeline = this[timelineName]
- this.populateRepeats(timeline, repeats)
-
// This makes sure that user timeline won't get data meant for other
// user. I.e. opening different user profiles makes request which could
// return data late after user already viewing different user profile
@@ -278,9 +278,7 @@ export const useTimelinesStore = defineStore('timelines', {
return
}
- if (!noIdUpdate) {
- this.updateTimelineExtremes(timeline, pagination)
- }
+ this.populateRepeats(timeline, repeats)
const filtered = statuses.filter((id) => !timeline.statusIds.has(id))
if (older) {
@@ -289,26 +287,36 @@ export const useTimelinesStore = defineStore('timelines', {
timeline.order.unshift(...filtered)
}
+ const newStatuses = new Set()
+
statuses.forEach((statusId) => {
const isNew = !timeline.statusIds.has(statusId)
timeline.statusIds.add(statusId)
if (isNew) {
- const seenBefore = this.checkSeenBefore(timeline, statusId)
- if (!seenBefore) {
- if (showImmediately) {
- // Add it directly to the visibleStatuses, don't change
- // newStatusCount
- timeline.visibleStatusIds.add(statusId)
- } else {
- // Just change newStatuscount
- timeline.newStatusCount += 1
- }
- } else {
- timeline.ignoredIds.add(statusId)
- }
+ newStatuses.add(statusId)
}
})
+
+ newStatuses.forEach((statusId) => {
+ const seenBefore = this.checkSeenBefore(timeline, statusId)
+ if (!seenBefore) {
+ if (showImmediately) {
+ // Add it directly to the visibleStatuses, don't change
+ // newStatusCount
+ timeline.visibleStatusIds.add(statusId)
+ } else {
+ // Just change newStatuscount
+ timeline.newStatusCount += 1
+ }
+ } else {
+ timeline.ignoredIds.add(statusId)
+ }
+ })
+
+ if (!noIdUpdate) {
+ this.updateTimelineExtremes(timeline, pagination)
+ }
},
onStreamMessage(timelineName, argument, event) {
this.addStatusesToTimeline(timelineName, argument, {
@@ -347,7 +355,7 @@ export const useTimelinesStore = defineStore('timelines', {
// If it's the only reprööt then we've never seen post before
if (knownRepeats.size === 1) return false
// If we're working on oldest known reprööt then we've never seen it before
- return first(knownRepeats) !== statusId
+ return knownRepeats.values().next().value !== statusId
},
// Poll & Push
@@ -414,7 +422,7 @@ export const useTimelinesStore = defineStore('timelines', {
},
// Queues & Timeline manip
- updateTimelineExtremes(timeline, pagination = {}) {
+ updateTimelineExtremes(timeline, pagination = {}, force = false) {
// Can't use Math.min/max because it doesn't work with string (duh)
const minNew = pagination.maxId ?? last(timeline.order) ?? ''
const maxNew = pagination.minId ?? first(timeline.order) ?? ''
@@ -422,10 +430,10 @@ export const useTimelinesStore = defineStore('timelines', {
const newer = maxNew > timeline.maxId
const older = minNew < timeline.minId
- if (newer || timeline.maxId === '') {
+ if (force || newer || timeline.maxId === '') {
timeline.maxId = maxNew
}
- if (older || timeline.minId === '') {
+ if (force || older || timeline.minId === '') {
timeline.minId = minNew
}
@@ -442,7 +450,8 @@ export const useTimelinesStore = defineStore('timelines', {
timeline.visibleStatusIds = new Set([
...timeline.order.filter((id) => !timeline.ignoredIds.has(id)),
])
- this.updateTimelineExtremes(timeline)
+ this.updateTimelineExtremes(timeline, {}, true)
+ timeline.fetcher.resetBottomedOut()
},
syncOrder(timeline) {
timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id))
@@ -451,8 +460,10 @@ export const useTimelinesStore = defineStore('timelines', {
this[timeline].reloadNeeded = true
},
requireReloadAll() {
- Object.keys(this).forEach((timeline) => {
- this[timeline].reloadNeeded = true
+ TIMELINES.forEach((timelineName) => {
+ const timeline = this[timelineName]
+
+ timeline.reloadNeeded = true
})
},
diff --git a/src/stores/users.js b/src/stores/users.js
index 305d0fc5b..87b177dfa 100644
--- a/src/stores/users.js
+++ b/src/stores/users.js
@@ -278,15 +278,21 @@ export const useUsersStore = defineStore('users', {
const result = await promise
- if (result) {
- const { id, screen_name } = result
+ try {
+ if (result) {
+ const { id, screen_name } = result
- // Save promise for future use
- this.fetchesIds.set(id, promise)
- this.fetchesNames.set(screen_name, promise)
- return this.users.get(id)
- } else {
- return null
+ // Save promise for future use
+ this.fetchesIds.set(id, promise)
+ this.fetchesNames.set(screen_name, promise)
+ return this.users.get(id)
+ } else {
+ return null
+ }
+ } catch (e) {
+ console.error(`Failed fetching user ${identifier}`, e)
+ map.delete(identifier)
+ throw e
}
},
async fetchUser(id) {
@@ -520,7 +526,7 @@ export const useUsersStore = defineStore('users', {
/// Mute
muteUser(id, expiresIn = 0) {
- const predictedRelationship = this.relationships[id] || { id }
+ const predictedRelationship = this.relationships.get(id) || { id }
predictedRelationship.muting = true
this.updateUserRelationships({
optimism: true,
@@ -539,7 +545,7 @@ export const useUsersStore = defineStore('users', {
return Promise.all(data.map((d) => this.muteUser(d)))
},
unmuteUser(id) {
- const predictedRelationship = this.relationships[id] || { id }
+ const predictedRelationship = this.relationships.get(id) || { id }
predictedRelationship.muting = false
this.updateUserRelationships({
optimism: true,
@@ -556,7 +562,7 @@ export const useUsersStore = defineStore('users', {
/// Block
blockUser(id, expiresIn = 0) {
- const predictedRelationship = this.relationships[id] || { id }
+ const predictedRelationship = this.relationships.get(id) || { id }
this.updateUserRelationships({
optimism: true,
data: [predictedRelationship],