diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index 3077ed212..6296e3c9a 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -62,7 +62,7 @@ const EmojiReactions = {
},
async fetchEmojiReactionsIfMissing() {
const hasNoAccounts = this.status.emoji_reactions.find((r) => !r.accounts)
- if (!hasNoAccounts) {
+ if (hasNoAccounts) {
return await useStatusesStore().fetchEmojiReactions(this.status.id)
}
},
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
index 3973522b0..33f0d9db7 100644
--- a/src/components/mention_link/mention_link.vue
+++ b/src/components/mention_link/mention_link.vue
@@ -8,20 +8,15 @@
:href="url"
class="original"
target="_blank"
- >
-
-
-
+ v-html="content"
+ />
#{{ hashtag.name }}
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 16625235d..387146db1 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -128,7 +128,6 @@ const Status = {
return {
replying: false,
unmuted: false,
- userExpanded: false,
mediaPlaying: new Set(),
error: null,
headTailLinks: null,
@@ -524,9 +523,6 @@ const Status = {
toggleMute() {
this.unmuted = !this.unmuted
},
- toggleUserExpanded() {
- this.userExpanded = !this.userExpanded
- },
generateUserProfileLink(id, name) {
return generateProfileLink(
id,
@@ -576,6 +572,12 @@ const Status = {
this.$emit('heightChange')
},
},
+ unmuted() {
+ this.$emit('heightChange')
+ },
+ error() {
+ this.$emit('heightChange')
+ },
replying() {
this.$emit('heightChange')
},
@@ -604,7 +606,6 @@ const Status = {
},
isSuspendable: function (suspend) {
this.$emit('suspendableStateChange', { id: this.status.id, suspend })
- this.$emit('heightChange')
},
},
}
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 0db67f24b..5171f7657 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -549,6 +549,7 @@
@posted="closeReplyForm"
@draft-done="closeReplyForm"
@close-accepted="closeReplyForm"
+ @resize="$emit('heightChange')"
/>
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index 029693651..3c7e33060 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -239,7 +239,7 @@ const Timeline = {
const bodyBRect = document.body.getBoundingClientRect()
const height = Math.max(bodyBRect.height, -bodyBRect.y)
if (
- !this.timeline.fetcher.loadingOlder.value &&
+ !this.timeline.fetcher.loadingOlder &&
window.innerHeight + window.pageYOffset >= height - 750
) {
this.fetchOlderStatuses()
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index c8701fe66..b80aa8fb8 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -12,7 +12,7 @@ const UserAvatar = {
props: {
// UserID of a user to show avatar of
userId: {
- required: false, // You can pass null to just render a placeholder
+ required: true,
type: String,
},
// Use less space and use alternative roundness
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index 613d224a0..d9e96d52d 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -70,7 +70,6 @@
&.-placeholder {
background-color: var(--background);
- border: 1px solid var(--border)
}
}
diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js
index b87004e44..0f7f93556 100644
--- a/src/components/who_to_follow_panel/who_to_follow_panel.js
+++ b/src/components/who_to_follow_panel/who_to_follow_panel.js
@@ -30,8 +30,8 @@ function showWhoToFollow(panel, reply) {
})
}
-function getWhoToFollow(panel) {
- const credentials = panel.$useUsersStore().currentUser.credentials
+function getWhoToFollow() {
+ const credentials = useOAuthStore().token
if (credentials) {
panel.usersToFollow.forEach((toFollow) => {
toFollow.name = 'Loading...'
@@ -66,7 +66,7 @@ const WhoToFollowPanel = {
watch: {
user: function () {
if (this.suggestionsEnabled) {
- getWhoToFollow(this)
+ getWhoToFollow()
}
},
},
@@ -77,7 +77,7 @@ const WhoToFollowPanel = {
id: 0,
}))
if (this.suggestionsEnabled) {
- getWhoToFollow(this)
+ getWhoToFollow()
}
},
}
diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js
index d99c17ed8..a5c0d67cd 100644
--- a/src/services/chat_utils/chat_utils.js
+++ b/src/services/chat_utils/chat_utils.js
@@ -11,13 +11,14 @@ export const maybeShowChatNotification = (chat) => {
title: chat.account.name,
icon: chat.account.profile_image_url,
body: chat.lastMessage.content,
+ type: 'chatMention',
}
if (chat.lastMessage.attachment?.type === 'image') {
opts.image = chat.lastMessage.attachment.preview_url
}
- showDesktopNotification(window.vuex.state, opts)
+ showDesktopNotification(opts)
}
export const buildFakeMessage = ({
diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js
index 00b8699ac..41038573d 100644
--- a/src/services/status_poster/status_poster.service.js
+++ b/src/services/status_poster/status_poster.service.js
@@ -2,7 +2,7 @@ import { map } from 'lodash'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useTimelinesStore } from 'src/stores/timelines.js'
-import { useUsersStore } from 'src/stores/users.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
import {
editStatus as apiEditStatus,
@@ -28,7 +28,7 @@ const postStatus = ({
const mediaIds = map(media, 'id')
return apiPostStatus({
- credentials: useUsersStore().currentUser.credentials,
+ credentials: useOAuthStore().token,
status,
spoilerText,
visibility,
@@ -71,7 +71,7 @@ const editStatus = ({
return apiEditStatus({
id: statusId,
- credentials: useUsersStore().currentUser.credentials,
+ credentials: useOAuthStore().token,
status,
spoilerText,
sensitive,
@@ -101,12 +101,12 @@ const editStatus = ({
}
const uploadMedia = ({ store, formData }) => {
- const credentials = useUsersStore().currentUser.credentials
+ const credentials = useOAuthStore().token
return apiUploadMedia({ credentials, formData }).then(({ data }) => data)
}
const setMediaDescription = ({ store, id, description }) => {
- const credentials = useUsersStore().currentUser.credentials
+ const credentials = useOAuthStore().token
return apiSetMediaDescription({ credentials, id, description }).then(
({ data }) => data,
)
diff --git a/src/stores/chats.js b/src/stores/chats.js
index 9da01c76b..82bb638dd 100644
--- a/src/stores/chats.js
+++ b/src/stores/chats.js
@@ -37,7 +37,10 @@ export const useChatsStore = defineStore('chats', {
actions: {
attachSocket() {
const et = new EventTarget()
- const socket = { et }
+ const socket = {
+ name: 'chats',
+ et,
+ }
et.addEventListener('pleroma:chat_update', this.updateChat)
@@ -83,9 +86,6 @@ export const useChatsStore = defineStore('chats', {
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
- if (isNewMessage && chat.unread) {
- maybeShowChatNotification(chat)
- }
} else {
this.chatList.data.push(updatedChat)
this.chatList.idStore[updatedChat.id] = updatedChat
@@ -98,16 +98,16 @@ export const useChatsStore = defineStore('chats', {
chat.unread = 0
}
},
- updateChat({ chat: updatedChat }) {
+ updateChat({ data: { chatUpdate: updatedChat } }) {
const chat = getChatById(this, updatedChat.id)
if (chat) {
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at
- }
- if (!chat) {
+ } else {
this.chatList.data.unshift(updatedChat)
}
+ maybeShowChatNotification(chat)
this.chatList.idStore[updatedChat.id] = updatedChat
},
deleteChat(id) {
diff --git a/src/stores/instance.js b/src/stores/instance.js
index 123f0b819..12480e8c1 100644
--- a/src/stores/instance.js
+++ b/src/stores/instance.js
@@ -14,6 +14,7 @@ import {
import { useInterfaceStore } from 'src/stores/interface.js'
import { useUsersStore } from 'src/stores/users.js'
+import { useOAuthStore } from 'src/stores/oauth.js'
import { fetchKnownDomains } from 'src/api/public.js'
@@ -213,7 +214,7 @@ export const useInstanceStore = defineStore('instance', {
async getKnownDomains() {
try {
const { data } = await fetchKnownDomains({
- credentials: useUsersStore().currentUser.credentials,
+ credentials: useOAuthStore().token
})
this.knownDomains = data
} catch (e) {
diff --git a/src/stores/interface.js b/src/stores/interface.js
index 0eba6f158..e87a0ecd9 100644
--- a/src/stores/interface.js
+++ b/src/stores/interface.js
@@ -96,7 +96,10 @@ export const useInterfaceStore = defineStore('interface', {
actions: {
attachSocket() {
const et = new EventTarget()
- const socket = { et }
+ const socket = {
+ name: 'interface',
+ et,
+ }
et.addEventListener('open', this.onStreamConnect)
et.addEventListener('close', this.onStreamDisconnect)
@@ -854,7 +857,7 @@ export const useInterfaceStore = defineStore('interface', {
},
unregisterPushNotifications() {
- const token = this.currentUser.credentials
+ const token = useOAuthStore().token
unregisterPushNotifications(token)
},
diff --git a/src/stores/notifications.js b/src/stores/notifications.js
index a12ce8133..3405fb352 100644
--- a/src/stores/notifications.js
+++ b/src/stores/notifications.js
@@ -31,7 +31,9 @@ export const defaultState = () => ({
statusIdStore: new Set(),
socket: null,
streaming: false,
+ fetching: true,
fetcher: null,
+ paused: false,
})
export const useNotificationsStore = defineStore('notifications', {
@@ -40,14 +42,29 @@ export const useNotificationsStore = defineStore('notifications', {
// Init
attachSocket() {
const et = new EventTarget()
- const socket = { et }
+ const socket = {
+ name: 'notifications',
+ et,
+ }
et.addEventListener('notification', this.addNewNotifications)
et.addEventListener('open', this.onStreamConnect)
et.addEventListener('close', this.onStreamDisconnect)
- useStreamingStore().addSubscriber(socket)
this.socket = socket
+ useStreamingStore().addSubscriber(this.socket)
+ },
+ pause() {
+ this.paused = true
+ if (this.fetcher && this.fetching) {
+ this.stopFetching('Notifications paused')
+ }
+ },
+ resume() {
+ this.paused = false
+ if (this.fetcher && this.fetching) {
+ this.startFetching('Notifications resumed')
+ }
},
activate() {
this.attachSocket()
@@ -62,7 +79,7 @@ export const useNotificationsStore = defineStore('notifications', {
this.startFetching('Notifications activated')
},
deactivate() {
- if (!this.streaming) {
+ if (this.fetching) {
this.stopFetching('Notifications deactivated')
}
@@ -77,6 +94,7 @@ export const useNotificationsStore = defineStore('notifications', {
Object.keys(blankState).forEach((k) => {
this[k] = blankState[k]
})
+ console.log('[Notifications] Deactivated', this.fetcher)
},
// Poll & Push
@@ -91,20 +109,30 @@ export const useNotificationsStore = defineStore('notifications', {
this.startFetching('Socket disconnected')
},
startFetching(reason) {
+ if (this.paused) {
+ console.debug(
+ '[Notificatiosn] NOT Starting notifications fetcher because it is paused',
+ 'Original Reason:',
+ reason,
+ )
+ return
+ }
console.debug(
- '[Notifications] Starting fetching notifications',
+ '[Notifications] Starting notifications fetcher',
'Reason:',
reason,
)
this.fetcher.startFetching()
+ this.fetching = true
},
stopFetching(reason) {
+ this.fetcher.stopFetching()
+ this.fetching = false
console.debug(
- '[Notifications] Stopped fetching notifications',
+ '[Notifications] Stopped notifications fetcher',
'Reason:',
reason,
)
- this.fetcher.stopFetching()
},
// Updates
@@ -223,7 +251,7 @@ export const useNotificationsStore = defineStore('notifications', {
case 'follow_request':
break
default:
- this.markSingleNotificationAsSeen({ id })
+ this.markSingleNotificationAsSeen(id)
}
}
},
diff --git a/src/stores/search.js b/src/stores/search.js
index ad85712c4..cd7862d23 100644
--- a/src/stores/search.js
+++ b/src/stores/search.js
@@ -19,7 +19,7 @@ export const useSearchStore = defineStore('search', {
credentials: useOAuthStore().token,
})
- const { accounts, statuses } = data
+ const { accounts, statuses, hashtags } = data
useUsersStore().addNewUsers({
...rest,
@@ -36,6 +36,7 @@ export const useSearchStore = defineStore('search', {
useStatusesStore().allStatuses.get(s.id),
)
output.accounts = accounts.map((s) => useUsersStore().findUser(s.id))
+ output.hashtags = hashtags ?? []
return output
},
diff --git a/src/stores/statuses.js b/src/stores/statuses.js
index 8836a0a77..337cb399d 100644
--- a/src/stores/statuses.js
+++ b/src/stores/statuses.js
@@ -49,6 +49,7 @@ export const useStatusesStore = defineStore('statuses', {
data.forEach((id) => this.setDeleted(id))
const socket = {
+ name: 'statuses',
et,
handlers: {
handleUpdate,
@@ -64,15 +65,9 @@ export const useStatusesStore = defineStore('statuses', {
this.socket = socket
},
resetStatuses() {
- this.socket.et.removeEventListener('update', this.socket.handleUpdate)
- this.socket.et.removeEventListener(
- 'status.update',
- this.socket.handleUpdate,
- )
- this.socket.et.removeEventListener('delete', this.socket.handleDelete)
-
const emptyState = defaultState()
Object.entries(emptyState).forEach(([key, value]) => {
+ if (key === 'socket') return
this[key] = value
})
},
@@ -154,7 +149,7 @@ export const useStatusesStore = defineStore('statuses', {
const newStatus = {
...old,
- ...neu,
+ ...Object.fromEntries(Object.entries(neu).filter(([, v]) => v !== undefined)),
user,
}
diff --git a/src/stores/streaming.js b/src/stores/streaming.js
index 5129a571d..1b4d07292 100644
--- a/src/stores/streaming.js
+++ b/src/stores/streaming.js
@@ -131,6 +131,7 @@ export const useStreamingStore = defineStore('streaming', {
},
getSubArgs(stream) {
+ if (stream === undefined) return undefined
const argumentKey = ARGUMENT_MAP[stream.name]
const args = argumentKey
? {
diff --git a/src/stores/timelines.js b/src/stores/timelines.js
index fe098587a..190396f48 100644
--- a/src/stores/timelines.js
+++ b/src/stores/timelines.js
@@ -16,9 +16,11 @@ const emptyTl = (name, argument = null) => {
maxId: '',
minId: '',
streaming: false,
+ fetching: false,
reloadNeeded: false,
fetcher: null,
socket: null,
+ paused: false,
}
const property = ARGUMENT_MAP[name]
@@ -105,16 +107,16 @@ export const useTimelinesStore = defineStore('timelines', {
const openHandler = () => this.onStreamConnect(timelineName, argument)
const closeHandler = () =>
this.onStreamDisconnect(timelineName, argument)
- const messageHandler =
- () =>
- ({ detail: message }) =>
- this.onStreamMessage(timelineName, argument, message)
+ const messageHandler = (message) => {
+ this.onStreamMessage(timelineName, argument, message)
+ }
et.addEventListener('open', openHandler)
et.addEventListener('close', closeHandler)
et.addEventListener('update', messageHandler)
timeline.socket = {
+ name: 'timelines',
stream: {
name: streamName,
argument,
@@ -133,7 +135,7 @@ export const useTimelinesStore = defineStore('timelines', {
deactivate(timelineName, persistent) {
const timeline = this[timelineName]
if (timeline.persistent && !persistent) return
- if (!timeline.streaming) {
+ if (timeline.fetching) {
this.stopFetchingTimeline(timelineName, 'Timeline deactivation')
}
@@ -177,6 +179,48 @@ export const useTimelinesStore = defineStore('timelines', {
})
},
+ // Pause
+ pause(name) {
+ const timeline = this[name]
+ timeline.paused = true
+ console.debug(
+ '[Timelines] Pausing timeline',
+ name,
+ )
+ if (timeline.fetcher && timeline.fetching) {
+ timeline.fetcher.stopFetching()
+ }
+ },
+ resume(name) {
+ const timeline = this[name]
+ timeline.paused = false
+ console.debug(
+ '[Timelines] Resuming timeline',
+ name,
+ )
+ if (timeline.fetcher && timeline.fetching) {
+ timeline.fetcher.startFetching()
+ }
+ },
+ pauseAll() {
+ TIMELINES.forEach((name) => {
+ try {
+ this.pause(name)
+ } catch (e) {
+ console.error(`[Timelines] Failed to pause timeline ${name}:`, e)
+ }
+ })
+ },
+ resumeAll() {
+ TIMELINES.forEach((name) => {
+ try {
+ this.resume(name)
+ } catch (e) {
+ console.error(`[Timelines] Failed to pause timeline ${name}:`, e)
+ }
+ })
+ },
+
// Update stuff
addStatusesToTimeline(
timelineName,
@@ -231,7 +275,7 @@ export const useTimelinesStore = defineStore('timelines', {
},
onStreamMessage(timeline, argument, event) {
this.addStatusesToTimeline(timeline, argument, {
- statuses: [event.data.status.id],
+ statuses: event.data.map(({ id }) => id),
})
},
@@ -247,15 +291,28 @@ export const useTimelinesStore = defineStore('timelines', {
this.startFetchingTimeline(timeline, argument, 'Socket disconnected')
},
startFetchingTimeline(timelineName, argument, reason) {
+ const timeline = this[timelineName]
+ console.log('[Timelines]', toValue(timeline))
+ if (timeline.paused) {
+ console.debug(
+ '[Timelines] NOT Starting timeline fetcher because it is paused',
+ timelineName,
+ argument,
+ 'Original Reason:',
+ reason,
+ )
+ return
+ }
+
console.debug(
- '[Timelines] Starting fetching timeline',
+ '[Timelines] Starting timeline fetcher',
timelineName,
argument,
'Reason:',
reason,
)
- const timeline = this[timelineName]
timeline.fetcher.startFetching()
+ timeline.fetching = true
},
stopFetchingTimeline(timelineName, reason) {
const timeline = this[timelineName]
@@ -275,6 +332,7 @@ export const useTimelinesStore = defineStore('timelines', {
'Reason:',
reason,
)
+ timeline.fetching = false
}
},
diff --git a/src/stores/users.js b/src/stores/users.js
index 10d9222c9..9b824bf04 100644
--- a/src/stores/users.js
+++ b/src/stores/users.js
@@ -2,6 +2,8 @@ import Cookies from 'js-cookie'
import { last } from 'lodash'
import { defineStore } from 'pinia'
+import { WSConnectionStatus } from 'src/api/websocket.js'
+
import { useAnnouncementsStore } from 'src/stores/announcements.js'
import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js'
import { useChatsStore } from 'src/stores/chats.js'
@@ -615,12 +617,15 @@ export const useUsersStore = defineStore('users', {
user.domainMutes = new Set()
this.lastLoginName = user.screen_name
- this.currentUser = user
+ useTimelinesStore().deactivateAll()
+ useStatusesStore().resetStatuses()
this.users = new Map()
this.usersByName = new Map()
this.usersByURL = new Map()
this.relationships = new Map()
+ this.currentUser = user
+ this.addNewUsers({ data: user, ...rest })
useInterfaceStore().onLogin()
useSyncConfigStore()
@@ -634,7 +639,6 @@ export const useUsersStore = defineStore('users', {
})
useUserHighlightStore().initUserHighlight(user)
- this.addNewUsers({ data: user, ...rest })
useEmojiStore().fetchEmoji()
@@ -660,8 +664,8 @@ export const useUsersStore = defineStore('users', {
}
// DMs and Home
- useNotificationsStore().activate()
useTimelinesStore().activatePersistents()
+ useNotificationsStore().activate()
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
// Start fetching chats
@@ -708,6 +712,16 @@ export const useUsersStore = defineStore('users', {
const store = window.vuex
const oauth = useOAuthStore()
+ // Pause fetching
+ useNotificationsStore().pause()
+ useTimelinesStore().pauseAll()
+
+ // Pause-less stores
+ useAnnouncementsStore().stopFetching()
+ useListsStore().stopFetching()
+ useBookmarkFoldersStore().stopFetching()
+ store?.dispatch('stopFetchingFollowRequests')
+
// NOTE: No need to verify the app still exists, because if it doesn't,
// the token will be invalid too
return oauth
@@ -722,28 +736,53 @@ export const useUsersStore = defineStore('users', {
return revokeToken(params)
})
.then(() => {
+ oauth.clearToken()
+
this.currentUser = null
this.lastLoginName = null
+ useNotificationsStore().deactivate()
+
+ // Full reset on logout success
+ useTimelinesStore().deactivateAll()
+ useStatusesStore().resetStatuses()
+ useChatsStore().resetChats()
+
this.users = new Map()
this.usersByName = new Map()
this.usersByURL = new Map()
this.relationships = new Map()
- useNotificationsStore().deactivate()
- useAnnouncementsStore().stopFetching()
- useListsStore().stopFetching()
- useBookmarkFoldersStore().stopFetching()
- store?.dispatch('stopFetchingFollowRequests')
- useTimelinesStore().deactivateAll()
- useStatusesStore().resetStatuses()
- if (useMergedConfigStore().mergedConfig.useStreamingApi) {
+
+ // Socket is most likely already closed by server
+ if (
+ useMergedConfigStore().mergedConfig.useStreamingApi
+ && useStreamingStore().state !== WSConnectionStatus.CLOSED
+ ) {
useStreamingStore().stopSocket()
}
- useChatsStore().resetChats()
- oauth.clearToken()
+
Cookies.remove('__Host-pleroma_key', { path: '/' })
useInterfaceStore().onLogout()
})
+ .catch((e) => {
+ useInterfaceStore().pushGlobalNotice({
+ messageKey: 'user.logout_failure',
+ messageArgs: {
+ error: e,
+ },
+ level: 'error',
+ })
+ console.error('Logout error!', e)
+
+ useAnnouncementsStore().startFetching()
+ useListsStore().startFetching()
+ useBookmarkFoldersStore().startFetching()
+ store?.dispatch('startFetchingFollowRequests')
+ })
+ .finally(() => {
+ useNotificationsStore().resume()
+ useTimelinesStore().resumeAll()
+ })
},
},
persist: {
diff --git a/test/unit/specs/components/rich_content.spec.js b/test/unit/specs/components/rich_content.spec.js
index 81ce72e87..19bfdc035 100644
--- a/test/unit/specs/components/rich_content.spec.js
+++ b/test/unit/specs/components/rich_content.spec.js
@@ -354,13 +354,11 @@ describe('RichContent', () => {
'