Compare commits
54 commits
56d962d3aa
...
410e3cff41
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
410e3cff41 | ||
|
|
53498072e5 | ||
|
|
9a263692ea | ||
|
|
1e60d5ada0 | ||
|
|
39f7d522e3 | ||
|
|
5788d3e8ec | ||
|
|
3b856e94da | ||
|
|
3589a5302e | ||
|
|
abe6af133f | ||
|
|
b3754b26c4 | ||
|
|
5ea41244c1 | ||
|
|
64cc748a9c | ||
|
|
43ddbfd652 | ||
|
|
b75bd5ae73 | ||
|
|
725b2f5387 | ||
|
|
590ca6fb6f | ||
|
|
8e63f189fc | ||
|
|
32c27b85b0 | ||
|
|
f2f15a0c02 | ||
|
|
456f25e94e | ||
|
|
5b55901611 | ||
|
|
a9ce0eeb1e | ||
|
|
cdaff8d8e0 | ||
|
|
ce93f51d5b | ||
|
|
8d49ef11ff | ||
|
|
5dc3a87a97 | ||
|
|
7feb071d0b | ||
|
|
4114349b0d | ||
|
|
75d38d3f2b | ||
|
|
42900f6336 | ||
|
|
8f9635f5b3 | ||
|
|
3774a0f982 | ||
|
|
cbda3b3d65 | ||
|
|
e7b11fdb85 | ||
|
|
9a659a2f1f | ||
|
|
4f6c72894e | ||
|
|
cdf3a5d959 | ||
|
|
46085528b9 | ||
|
|
700be4b08f | ||
|
|
22a04b2feb | ||
|
|
dc4a8e506d | ||
|
|
353f07a9ea | ||
|
|
634ad0df23 | ||
|
|
e0d6d5ac85 | ||
|
|
224b9faa02 | ||
|
|
7b27f337c6 | ||
|
|
282ec82bf1 | ||
|
|
8ca71beef0 | ||
|
|
08b06e7c01 | ||
|
|
b124d2b8ea | ||
|
|
87f8f3fcaa | ||
|
|
f63f65767f | ||
|
|
d64821e93b | ||
|
|
5854b222bd |
55 changed files with 430 additions and 251 deletions
|
|
@ -90,7 +90,7 @@ const AccountActions = {
|
|||
name: 'chat',
|
||||
params: {
|
||||
username: useUsersStore().currentUser.screen_name,
|
||||
recipient_id: this.user.id,
|
||||
chatUserId: this.user.id,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
const AvatarList = {
|
||||
props: ['users'],
|
||||
props: {
|
||||
userIds: Set,
|
||||
},
|
||||
computed: {
|
||||
slicedUsers() {
|
||||
return this.users ? this.users.slice(0, 15) : []
|
||||
return [...(this.userIds ?? [])]
|
||||
.slice(0, 15)
|
||||
.map((id) => useUsersStore().findUser(id))
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
{{ $t('admin_dash.users.labels.handle_colon') }}
|
||||
{{ ' ' }}
|
||||
</strong>
|
||||
<user-link
|
||||
<UserLink
|
||||
class="basic-user-card-screen-name"
|
||||
:user="user"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
|||
import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||
|
||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||
import { useSearchStore } from 'src/stores/search.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
import { chats } from 'src/api/chats.js'
|
||||
|
|
@ -50,7 +51,7 @@ const chatNew = {
|
|||
this.$emit('cancel')
|
||||
},
|
||||
goToChat(user) {
|
||||
this.$router.push({ name: 'chat', params: { recipient_id: user.id } })
|
||||
this.$router.push({ name: 'chat', params: { chatUserId: user.id } })
|
||||
},
|
||||
onInput() {
|
||||
this.search(this.query)
|
||||
|
|
@ -71,7 +72,7 @@ const chatNew = {
|
|||
this.loading = true
|
||||
this.userIds = []
|
||||
this.$store
|
||||
this.useSearchStore()
|
||||
useSearchStore()
|
||||
.search({ q: query, resolve: true, type: 'accounts' })
|
||||
.then((data) => {
|
||||
this.loading = false
|
||||
|
|
|
|||
|
|
@ -472,8 +472,8 @@ const Chat = {
|
|||
|
||||
// Clear any known pending messages
|
||||
if (message.idempotency_key) {
|
||||
if (this.pendingMessagesIndex[message.idempotencyKeyIndex]) {
|
||||
delete this.pendingMessagesIndex[message.idempotencyKeyIndex]
|
||||
if (this.pendingMessagesIndex[message.idempotency_key]) {
|
||||
delete this.pendingMessagesIndex[message.idempotency_key]
|
||||
this.pendingMessages = this.pendingMessages.filter(
|
||||
({ idempotency_key }) =>
|
||||
idempotency_key !== message.idempotency_key,
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ const conversation = {
|
|||
|
||||
return [...conversation.keys()]
|
||||
.map((k) => useStatusesStore().allStatuses.get(k))
|
||||
.filter((status) => status.type != 'repeat') // Old backend behavior?
|
||||
.toSorted(sortById)
|
||||
},
|
||||
statusMap() {
|
||||
|
|
@ -621,6 +622,7 @@ const conversation = {
|
|||
},
|
||||
updateVirtualHeight() {
|
||||
if (this.hide) return // no updates when not rendering
|
||||
if (!this.status) return // not loaded yet
|
||||
this.$nextTick(() => {
|
||||
this.virtualHeight = this.$refs.body.getBoundingClientRect().height
|
||||
this.$emit('update:virtualHeight', {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { get } from 'lodash'
|
||||
import { mapState } from 'pinia'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import Modal from 'src/components/modal/modal.vue'
|
||||
|
|
@ -19,18 +20,16 @@ const EditStatusModal = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
modalActivated() {
|
||||
return useEditStatusStore().modalActivated
|
||||
},
|
||||
isFormVisible() {
|
||||
return this.isLoggedIn && !this.resettingForm && this.modalActivated
|
||||
return this.loggedIn && !this.resettingForm && this.modalActivated
|
||||
},
|
||||
params() {
|
||||
return useEditStatusStore().params || {}
|
||||
},
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
},
|
||||
watch: {
|
||||
params(newVal, oldVal) {
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ const EmojiReactions = {
|
|||
},
|
||||
accountsForEmoji() {
|
||||
return this.status.emoji_reactions.reduce((acc, reaction) => {
|
||||
acc[reaction.name] = reaction.accounts || []
|
||||
acc.set(reaction.name, new Set(reaction.account_ids))
|
||||
return acc
|
||||
}, {})
|
||||
}, new Map())
|
||||
},
|
||||
loggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
</FALayers>
|
||||
</component>
|
||||
<UserListPopover
|
||||
:users="accountsForEmoji[reaction.name]"
|
||||
:user-ids="accountsForEmoji.get(reaction.name)"
|
||||
class="emoji-reaction-popover"
|
||||
:normal-button="true"
|
||||
:trigger-attrs="counterTriggerAttrs(reaction)"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const FollowCard = {
|
|||
},
|
||||
computed: {
|
||||
isMe() {
|
||||
return useUsersStore().currentUser.id === this.user.id
|
||||
return useUsersStore().currentUser?.id === this.user.id
|
||||
},
|
||||
loggedIn() {
|
||||
return useUsersStore().currentUser
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { debounce } from 'lodash'
|
|||
|
||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||
|
||||
import { useSearchStore } from 'src/stores/search.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
|
|
@ -32,7 +34,7 @@ const ListsUserSearch = {
|
|||
this.loading = true
|
||||
this.$emit('loading')
|
||||
this.userIds = []
|
||||
this.useSearchStore()
|
||||
useSearchStore()
|
||||
.search({
|
||||
q: query,
|
||||
resolve: true,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { debounce } from 'lodash'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { usePostStatusStore } from 'src/stores/post_status.js'
|
||||
|
|
@ -34,9 +35,6 @@ const MobilePostStatusButton = {
|
|||
window.removeEventListener('resize', this.handleOSK)
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return useUsersStore().loggedIn
|
||||
},
|
||||
isHidden() {
|
||||
if (HIDDEN_FOR_PAGES.has(this.$route.name)) {
|
||||
return true
|
||||
|
|
@ -52,6 +50,7 @@ const MobilePostStatusButton = {
|
|||
autohideFloatingPostButton() {
|
||||
return !!useMergedConfigStore().mergedConfig.autohideFloatingPostButton
|
||||
},
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
},
|
||||
watch: {
|
||||
autohideFloatingPostButton: function (isEnabled) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<button
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
class="MobilePostButton button-default new-status-button"
|
||||
:class="{ 'hidden': isHidden, 'always-show': isPersistent }"
|
||||
:title="$t('post_status.new_status')"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
class="Notification container -muted"
|
||||
>
|
||||
<small>
|
||||
<user-link
|
||||
<UserLink
|
||||
:user="notification.from_profile"
|
||||
:at="false"
|
||||
/>
|
||||
|
|
@ -215,7 +215,7 @@
|
|||
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
|
||||
class="follow-text"
|
||||
>
|
||||
<user-link
|
||||
<UserLink
|
||||
class="follow-name"
|
||||
:user="notification.from_profile"
|
||||
/>
|
||||
|
|
@ -249,7 +249,7 @@
|
|||
v-else-if="notification.type === 'move'"
|
||||
class="move-text"
|
||||
>
|
||||
<user-link
|
||||
<UserLink
|
||||
:user="notification.target"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { get } from 'lodash'
|
||||
import { mapState } from 'pinia'
|
||||
|
||||
import Modal from 'src/components/modal/modal.vue'
|
||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||
|
|
@ -17,18 +18,16 @@ const PostStatusModal = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
modalActivated() {
|
||||
return usePostStatusStore().modalActivated
|
||||
},
|
||||
isFormVisible() {
|
||||
return this.isLoggedIn && !this.resettingForm && this.modalActivated
|
||||
return this.loggedIn && !this.resettingForm && this.modalActivated
|
||||
},
|
||||
params() {
|
||||
return usePostStatusStore().params || {}
|
||||
},
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
},
|
||||
watch: {
|
||||
params(newVal, oldVal) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Modal
|
||||
v-if="isLoggedIn && !resettingForm"
|
||||
v-if="loggedIn && !resettingForm"
|
||||
:is-open="modalActivated"
|
||||
class="post-form-modal-view"
|
||||
@backdrop-clicked="closeModal"
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import Popover from 'src/components/popover/popover.vue'
|
|||
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
|
@ -28,13 +28,14 @@ const QuickFilterSettings = {
|
|||
path: 'replyVisibility',
|
||||
value: visibility,
|
||||
})
|
||||
useStatusesStore().requireReloadAll()
|
||||
useTimelinesStore().requireReloadAll()
|
||||
},
|
||||
openTab(tab) {
|
||||
useInterfaceStore().openSettingsModalTab(tab)
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
...mapState(useMergedConfigStore, ['mergedConfig']),
|
||||
...mapState(useInterfaceStore, {
|
||||
mobileLayout: (state) => state.layoutType === 'mobile',
|
||||
|
|
@ -55,9 +56,6 @@ const QuickFilterSettings = {
|
|||
return 'dropdown-item'
|
||||
}
|
||||
},
|
||||
loggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
replyVisibilitySelf: {
|
||||
get() {
|
||||
return this.mergedConfig.replyVisibility === 'self'
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ const QuickViewSettings = {
|
|||
...mapState(useInterfaceStore, {
|
||||
mobileLayout: (state) => state.layoutType === 'mobile',
|
||||
}),
|
||||
loggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
conversationDisplay: {
|
||||
get() {
|
||||
return this.mergedConfig.conversationDisplay
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
|
|||
import Quote from './quote.vue'
|
||||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useSearchStore } from 'src/stores/search.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -93,7 +94,7 @@ export default {
|
|||
this.$emit('update:id', notice[3])
|
||||
} else if (value) {
|
||||
this.loading = true
|
||||
this.useSearchStore()
|
||||
useSearchStore()
|
||||
.search({
|
||||
q: value,
|
||||
resolve: true,
|
||||
|
|
|
|||
|
|
@ -100,9 +100,6 @@ const SettingsModalAdminContent = {
|
|||
user() {
|
||||
return useUsersStore().currentUser
|
||||
},
|
||||
isLoggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
open() {
|
||||
return useInterfaceStore().settingsModalState !== 'hidden'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { mapState } from 'pinia'
|
||||
|
||||
import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
|
||||
import AppearanceTab from './tabs/appearance_tab.vue'
|
||||
import ClutterTab from './tabs/clutter_tab.vue'
|
||||
|
|
@ -75,9 +77,6 @@ const SettingsModalContent = {
|
|||
OldThemeTab,
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
open() {
|
||||
return useInterfaceStore().settingsModalState !== 'hidden'
|
||||
},
|
||||
|
|
@ -87,6 +86,7 @@ const SettingsModalContent = {
|
|||
expertLevel() {
|
||||
return useMergedConfigStore().mergedConfig.expertLevel
|
||||
},
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<GeneralTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
:label="$t('settings.profile_tab')"
|
||||
icon="user"
|
||||
data-tab-name="profile"
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<ProfileTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
:label="$t('settings.composing')"
|
||||
icon="pen-alt"
|
||||
data-tab-name="composing"
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<LayoutTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
:full-width="true"
|
||||
:label="$t('settings.notifications')"
|
||||
icon="bell"
|
||||
|
|
@ -73,7 +73,7 @@
|
|||
<FilteringTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
:label="$t('settings.mutes_and_blocks')"
|
||||
icon="eye-slash"
|
||||
data-tab-name="mutesAndBlocks"
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
<ClutterTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
:label="$t('settings.security_tab')"
|
||||
icon="lock"
|
||||
data-tab-name="security"
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
<SecurityTab />
|
||||
</div>
|
||||
<div
|
||||
v-if="isLoggedIn"
|
||||
v-if="loggedIn"
|
||||
:label="$t('settings.data_import_export_tab')"
|
||||
icon="download"
|
||||
data-tab-name="dataImportExport"
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ const AppearanceTab = {
|
|||
},
|
||||
computed: {
|
||||
isDefaultBackground() {
|
||||
return !useUsersStore().currentUser.background_image
|
||||
return !useUsersStore().currentUser?.background_image
|
||||
},
|
||||
switchInProgress() {
|
||||
return useInterfaceStore().themeChangeInProgress
|
||||
|
|
@ -283,7 +283,7 @@ const AppearanceTab = {
|
|||
instanceWallpaperUsed() {
|
||||
return (
|
||||
useInstanceStore().instanceIdentity.background &&
|
||||
!useUsersStore().currentUser.background_image
|
||||
!useUsersStore().currentUser?.background_image
|
||||
)
|
||||
},
|
||||
customThemeVersion() {
|
||||
|
|
|
|||
|
|
@ -162,9 +162,9 @@
|
|||
<div class="fun-monitor-display-bezel button-default">
|
||||
<div class="fun-monitor-display-screen input">
|
||||
<img
|
||||
v-if="backgroundPreview || user.background_image || instanceWallpaper"
|
||||
v-if="backgroundPreview || user?.background_image || instanceWallpaper"
|
||||
class="fun-monitor-display-screen-image"
|
||||
:src="backgroundPreview || user.background_image || instanceWallpaper"
|
||||
:src="backgroundPreview || user?.background_image || instanceWallpaper"
|
||||
>
|
||||
<div
|
||||
v-else
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import UnitSetting from '../helpers/unit_setting.vue'
|
|||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||
|
||||
const ClutterTab = {
|
||||
components: {
|
||||
|
|
@ -36,7 +36,7 @@ const ClutterTab = {
|
|||
// Updating nested properties
|
||||
watch: {
|
||||
replyVisibility() {
|
||||
useStatusesStore().requireReloadAll()
|
||||
useTimelinesStore().requireReloadAll()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import UnitSetting from '../helpers/unit_setting.vue'
|
|||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||
import { useInterfaceStore } from 'src/stores/interface'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||
|
||||
import {
|
||||
newExporter,
|
||||
|
|
@ -266,7 +266,7 @@ const FilteringTab = {
|
|||
// Updating nested properties
|
||||
watch: {
|
||||
replyVisibility() {
|
||||
useStatusesStore().requireReloadAll()
|
||||
useTimelinesStore().requireReloadAll()
|
||||
},
|
||||
muteFiltersObject() {
|
||||
this.muteFiltersDraftObject = cloneDeep(
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ const GeneralTab = {
|
|||
value: mode,
|
||||
label: this.$t(`settings.absolute_time_format_12h_${mode}`),
|
||||
})),
|
||||
emailLanguage: useUsersStore().currentUser.language || [''],
|
||||
emailLanguage: useUsersStore().currentUser?.language || [''],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
@ -72,6 +72,9 @@ const GeneralTab = {
|
|||
useLocalConfigStore().set({ path, value })
|
||||
},
|
||||
toggleStreaming(value) {
|
||||
// Streaming is not available for the unauthenticated
|
||||
if (!useOAuthStore().token) return
|
||||
|
||||
if (value) {
|
||||
useStreamingStore().initSocket()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { uniqBy } from 'lodash'
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
import AvatarList from 'src/components/avatar_list/avatar_list.vue'
|
||||
|
|
@ -137,13 +136,30 @@ const Status = {
|
|||
useScrobblesStore().getLatestScrobble(this.status.user.id)
|
||||
},
|
||||
computed: {
|
||||
// Whatever we're given to work with
|
||||
status() {
|
||||
return this.statusoid ?? useStatusesStore().allStatuses.get(this.statusId)
|
||||
},
|
||||
// Status repeated
|
||||
repeatedStatus() {
|
||||
if (this.status.retweeted_status === undefined) return undefined
|
||||
return useStatusesStore().allStatuses.get(this.status.retweeted_status.id)
|
||||
},
|
||||
// THE repeat
|
||||
repeatStatus() {
|
||||
if (this.isRepeat) {
|
||||
return this.status
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
mainStatus() {
|
||||
if (this.isRepeat) {
|
||||
return this.repeatedStatus
|
||||
} else {
|
||||
return this.status
|
||||
}
|
||||
},
|
||||
repeater() {
|
||||
return useUsersStore().findUser(this.status.user.id)
|
||||
},
|
||||
|
|
@ -152,7 +168,7 @@ const Status = {
|
|||
},
|
||||
showReasonMutedThread() {
|
||||
return (
|
||||
(this.mainStatus.thread_muted || this.mainSatus.reblog?.thread_muted) &&
|
||||
(this.mainStatus.thread_muted || this.repeatStatus?.thread_muted) &&
|
||||
!this.inConversation
|
||||
)
|
||||
},
|
||||
|
|
@ -179,6 +195,12 @@ const Status = {
|
|||
useUserHighlightStore().get(this.repeater.screen_name),
|
||||
)
|
||||
},
|
||||
favoritedBy() {
|
||||
return useStatusesStore().favs.get(this.mainStatus.id) ?? new Set()
|
||||
},
|
||||
repeatedBy() {
|
||||
return useStatusesStore().repeats.get(this.mainStatus.id) ?? new Set()
|
||||
},
|
||||
userStyle() {
|
||||
if (this.noHeading) return
|
||||
return highlightStyle(useUserHighlightStore().get(this.user.screen_name))
|
||||
|
|
@ -212,13 +234,6 @@ const Status = {
|
|||
this.repeater.screen_name,
|
||||
)
|
||||
},
|
||||
mainStatus() {
|
||||
if (this.isRepeat) {
|
||||
return this.repeatedStatus
|
||||
} else {
|
||||
return this.status
|
||||
}
|
||||
},
|
||||
loggedIn() {
|
||||
return !!this.currentUser
|
||||
},
|
||||
|
|
@ -383,12 +398,7 @@ const Status = {
|
|||
}
|
||||
},
|
||||
combinedFavsAndRepeatsUsers() {
|
||||
// Use the status from the global status repository since favs and repeats are saved in it
|
||||
const combinedUsers = [].concat(
|
||||
this.mainStatus.favoritedBy,
|
||||
this.mainStatus.rebloggedBy,
|
||||
)
|
||||
return uniqBy(combinedUsers, 'id')
|
||||
return new Set([...this.favoritedBy, ...this.repeatedBy])
|
||||
},
|
||||
tags() {
|
||||
return [...this.status.tags]
|
||||
|
|
@ -403,7 +413,7 @@ const Status = {
|
|||
return (
|
||||
!this.hidePostStats &&
|
||||
this.focused &&
|
||||
(this.combinedFavsAndRepeatsUsers.length > 0 ||
|
||||
(this.combinedFavsAndRepeatsUsers.size > 0 ||
|
||||
this.mainStatus.quotes_count)
|
||||
)
|
||||
},
|
||||
|
|
@ -586,22 +596,14 @@ const Status = {
|
|||
},
|
||||
'mainStatus.repeat_num': function (num) {
|
||||
// refetch repeats when repeat_num is changed in any way
|
||||
if (
|
||||
this.focused &&
|
||||
this.mainStatus.rebloggedBy &&
|
||||
this.mainStatus.rebloggedBy.length !== num
|
||||
) {
|
||||
useStatusesStore().fetchRepeats(this.status.id)
|
||||
if (this.focused && this.repeatedBy.size !== num) {
|
||||
useStatusesStore().fetchRepeats(this.mainStatus.id)
|
||||
}
|
||||
},
|
||||
'mainStatus.fave_num': function (num) {
|
||||
// refetch favs when fave_num is changed in any way
|
||||
if (
|
||||
this.focused &&
|
||||
this.mainStatus.favoritedBy &&
|
||||
this.mainStatus.favoritedBy.length !== num
|
||||
) {
|
||||
useStatusesStore().fetchFavs(this.status.id)
|
||||
if (this.focused && this.favoritedBy.size !== num) {
|
||||
useStatusesStore().fetchFavs(this.mainStatus.id)
|
||||
}
|
||||
},
|
||||
isSuspendable: function (suspend) {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
class="fa-scale-110 fa-old-padding repeat-icon"
|
||||
icon="retweet"
|
||||
/>
|
||||
<user-link
|
||||
<UserLink
|
||||
:user="repeater"
|
||||
:at="false"
|
||||
/>
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
>
|
||||
{{ user.name }}
|
||||
</h4>
|
||||
<user-link
|
||||
<UserLink
|
||||
class="account-name"
|
||||
:title="user.screen_name_ui"
|
||||
:user="user"
|
||||
|
|
@ -464,26 +464,26 @@
|
|||
>
|
||||
<div class="stats">
|
||||
<UserListPopover
|
||||
v-if="mainStatus.rebloggedBy && mainStatus.rebloggedBy.length > 0"
|
||||
:users="mainStatus.rebloggedBy"
|
||||
v-if="repeatedBy.size > 0"
|
||||
:user-ids="repeatedBy"
|
||||
>
|
||||
<div class="stat-count">
|
||||
<a class="stat-title">{{ $t('status.repeats') }}</a>
|
||||
<div class="stat-number">
|
||||
{{ mainStatus.rebloggedBy.length }}
|
||||
{{ repeatedBy.size }}
|
||||
</div>
|
||||
</div>
|
||||
</UserListPopover>
|
||||
<UserListPopover
|
||||
v-if="mainStatus.favoritedBy && mainStatus.favoritedBy.length > 0"
|
||||
:users="mainStatus.favoritedBy"
|
||||
v-if="favoritedBy.size > 0"
|
||||
:user-ids="favoritedBy"
|
||||
>
|
||||
<div
|
||||
class="stat-count"
|
||||
>
|
||||
<a class="stat-title">{{ $t('status.favorites') }}</a>
|
||||
<div class="stat-number">
|
||||
{{ mainStatus.favoritedBy.length }}
|
||||
{{ favoritedBy.size }}
|
||||
</div>
|
||||
</div>
|
||||
</UserListPopover>
|
||||
|
|
@ -501,7 +501,7 @@
|
|||
</div>
|
||||
</router-link>
|
||||
<div class="avatar-row">
|
||||
<AvatarList :users="combinedFavsAndRepeatsUsers" />
|
||||
<AvatarList :user-ids="combinedFavsAndRepeatsUsers" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -102,7 +102,10 @@ const Timeline = {
|
|||
}
|
||||
},
|
||||
statusesToDisplay() {
|
||||
if (!this.virtualScrollingEnabled) return this.visibleStatusIds
|
||||
if (!this.virtualScrollingEnabled) {
|
||||
return new Set(this.filteredVisibleStatuses.map(({ id }) => 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)
|
||||
|
|
@ -214,6 +217,7 @@ const Timeline = {
|
|||
let err = statuses[approxIndex].getBoundingClientRect().y
|
||||
|
||||
// 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 =
|
||||
statuses[cappedScrollIndex].getBoundingClientRect().y
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
:timeline-name="timelineRef.name"
|
||||
/>
|
||||
<div
|
||||
v-if="timeline.fetcher.loadingNewer"
|
||||
v-if="timeline.fetcher.loadingNewer && !showLoadButton"
|
||||
class="loadingIndicator"
|
||||
>
|
||||
<FAIcon
|
||||
|
|
|
|||
|
|
@ -62,6 +62,22 @@ const TimelineMenu = {
|
|||
(route === 'bookmark-folder' || route === 'bookmarks')
|
||||
)
|
||||
},
|
||||
timelineName() {
|
||||
const route = this.$route.name
|
||||
if (route === 'tag-timeline') {
|
||||
return '#' + this.$route.params.tag
|
||||
}
|
||||
if (route === 'lists-timeline') {
|
||||
return useListsStore().findListTitle(this.$route.params.id)
|
||||
}
|
||||
if (route === 'bookmark-folder') {
|
||||
return useBookmarkFoldersStore().findBookmarkFolderName(
|
||||
this.$route.params.id,
|
||||
)
|
||||
}
|
||||
const i18nkey = timelineNames(this.bookmarkFolders)[this.$route.name]
|
||||
return i18nkey ? this.$t(i18nkey) : route
|
||||
},
|
||||
...mapState(useInstanceCapabilitiesStore, [
|
||||
'pleromaChatMessagesAvailable',
|
||||
'pleromaBookmarkFoldersAvailable',
|
||||
|
|
@ -103,22 +119,6 @@ const TimelineMenu = {
|
|||
event.stopPropagation()
|
||||
}
|
||||
},
|
||||
timelineName() {
|
||||
const route = this.$route.name
|
||||
if (route === 'tag-timeline') {
|
||||
return '#' + this.$route.params.tag
|
||||
}
|
||||
if (route === 'lists-timeline') {
|
||||
return useListsStore().findListTitle(this.$route.params.id)
|
||||
}
|
||||
if (route === 'bookmark-folder') {
|
||||
return useBookmarkFoldersStore().findBookmarkFolderName(
|
||||
this.$route.params.id,
|
||||
)
|
||||
}
|
||||
const i18nkey = timelineNames(this.bookmarkFolders)[this.$route.name]
|
||||
return i18nkey ? this.$t(i18nkey) : route
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
</template>
|
||||
<template #trigger>
|
||||
<span class="button-unstyled timeline-menu-title">
|
||||
<h1 class="title timeline-title">{{ timelineName() }}</h1>
|
||||
<h1 class="title timeline-title">{{ timelineName }}</h1>
|
||||
<span>
|
||||
<FAIcon
|
||||
size="sm"
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const UserAvatar = {
|
|||
return useUsersStore().findUser(this.userId)
|
||||
},
|
||||
showActorTypeIndicator() {
|
||||
return useMergedConfigStore().mergedConfig.hideBotIndication
|
||||
return !useMergedConfigStore().mergedConfig.hideBotIndication
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
|||
|
||||
import { useInstanceStore } from 'src/stores/instance.js'
|
||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||
import { useUsersStore } from 'src/stores/users.js'
|
||||
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
|
||||
|
|
@ -14,15 +15,22 @@ library.add(faCircleNotch)
|
|||
|
||||
const UserListPopover = {
|
||||
name: 'UserListPopover',
|
||||
props: ['users'],
|
||||
props: {
|
||||
userIds: Set,
|
||||
},
|
||||
components: {
|
||||
UnicodeDomainIndicator,
|
||||
Popover,
|
||||
UserAvatar,
|
||||
},
|
||||
computed: {
|
||||
users() {
|
||||
return [...this.userIds]
|
||||
.map((id) => useUsersStore().findUser(id))
|
||||
.filter(Boolean)
|
||||
},
|
||||
usersCapped() {
|
||||
return this.users.slice(0, 16)
|
||||
return [...this.users].slice(0, 16)
|
||||
},
|
||||
allowNonSquareEmoji() {
|
||||
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</template>
|
||||
<template #content>
|
||||
<div class="user-list-popover">
|
||||
<template v-if="users.length">
|
||||
<template v-if="userIds.size > 0">
|
||||
<router-link
|
||||
v-for="(user) in usersCapped"
|
||||
:key="user.id"
|
||||
|
|
|
|||
|
|
@ -49,11 +49,7 @@ const UserProfile = {
|
|||
return useTimelinesStore().media
|
||||
},
|
||||
isUs() {
|
||||
return (
|
||||
this.userId &&
|
||||
useUsersStore().currentUser.id &&
|
||||
this.userId === useUsersStore().currentUser.id
|
||||
)
|
||||
return this.userId && this.userId === useUsersStore().currentUser?.id
|
||||
},
|
||||
user() {
|
||||
return useUsersStore().findUser(this.userId)
|
||||
|
|
|
|||
|
|
@ -28,11 +28,8 @@ const UserReportingModal = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isLoggedIn() {
|
||||
return !!useUsersStore().currentUser
|
||||
},
|
||||
isOpen() {
|
||||
return this.isLoggedIn && this.reportModal.activated
|
||||
return this.loggedIn && this.reportModal.activated
|
||||
},
|
||||
userId() {
|
||||
return this.reportModal.userId
|
||||
|
|
@ -47,6 +44,7 @@ const UserReportingModal = {
|
|||
)
|
||||
},
|
||||
...mapState(useReportsStore, ['reportModal']),
|
||||
...mapState(useUsersStore, ['loggedIn']),
|
||||
},
|
||||
watch: {
|
||||
userId: 'resetState',
|
||||
|
|
|
|||
|
|
@ -24,11 +24,9 @@ const WhoToFollow = {
|
|||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then((result) => {
|
||||
const { data: externalUser } = result
|
||||
if (!externalUser.error) {
|
||||
useUsersStore().addNewUsers(result)
|
||||
this.users.push(externalUser)
|
||||
}
|
||||
const [user] = useUsersStore().addNewUsers(result)
|
||||
|
||||
this.users.push(user)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -400,9 +400,13 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
|
|||
|
||||
return {
|
||||
items: await Promise.all(
|
||||
users.map((user) => {
|
||||
useUsersStore().updateUserAdminData(user.id, user)
|
||||
return useUsersStore().findUser(user.id)
|
||||
users.map(async (user) => {
|
||||
const fullUser = await useUsersStore().fetchUserIfMissing({
|
||||
id: user.id,
|
||||
})
|
||||
|
||||
if (fullUser) useUsersStore().updateUserAdminData(user.id, user)
|
||||
return fullUser
|
||||
}),
|
||||
),
|
||||
count,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ export const useChatsStore = defineStore('chats', {
|
|||
updateChat(updatedChat) {
|
||||
const chat = this.data.get(updatedChat.id)
|
||||
if (chat) {
|
||||
const isNewMessage = chat.lastMessage !== updatedChat.lastMessage
|
||||
const isNewMessage =
|
||||
chat.lastMessage?.id !== updatedChat.lastMessage?.id
|
||||
chat.lastMessage = updatedChat.lastMessage
|
||||
chat.unread = updatedChat.unread
|
||||
chat.updated_at = updatedChat.updated_at
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ export const useListsStore = defineStore('lists', {
|
|||
},
|
||||
setLists(value) {
|
||||
this.allLists = value
|
||||
this.allListsObject = Object.fromEntries(
|
||||
value.map((list) => [list.id, list]),
|
||||
)
|
||||
},
|
||||
async createList({ title }) {
|
||||
return await createList({
|
||||
|
|
|
|||
|
|
@ -81,13 +81,15 @@ export const useNotificationsStore = defineStore('notifications', {
|
|||
pause() {
|
||||
this.paused = true
|
||||
if (this.fetcher && this.fetching) {
|
||||
this.stopFetching('Notifications paused')
|
||||
console.debug('[Notifications] Pausing notifications')
|
||||
this.fetcher.stopFetching()
|
||||
}
|
||||
},
|
||||
resume() {
|
||||
this.paused = false
|
||||
if (this.fetcher && this.fetching) {
|
||||
this.startFetching('Notifications resumed')
|
||||
console.debug('[Notifications] Resuming notifications')
|
||||
this.fetcher.startFetching()
|
||||
}
|
||||
},
|
||||
activate() {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ export const defaultState = () => ({
|
|||
conversations: new Map(),
|
||||
favorites: new Set(),
|
||||
socket: null,
|
||||
favs: new Map(),
|
||||
repeats: new Map(),
|
||||
})
|
||||
|
||||
export const useStatusesStore = defineStore('statuses', {
|
||||
|
|
@ -188,61 +190,69 @@ export const useStatusesStore = defineStore('statuses', {
|
|||
return fetchEmojiReactions({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: emojiReactions }) => {
|
||||
this.addEmojiReactionsBy({
|
||||
id,
|
||||
emojiReactions,
|
||||
}).then(({ data, timestamp }) => {
|
||||
const reactions = data.map((reaction) => {
|
||||
const users = useUsersStore().addNewUsers({
|
||||
timestamp,
|
||||
data: reaction.accounts,
|
||||
})
|
||||
|
||||
// Backend inconsistency - status data only has ids (account_ids)
|
||||
// but reactions data has full info (accounts)
|
||||
return {
|
||||
...reaction,
|
||||
accounts: users,
|
||||
account_ids: users.map(({ id }) => id),
|
||||
}
|
||||
})
|
||||
|
||||
this.addEmojiReactionsBy(id, reactions)
|
||||
})
|
||||
},
|
||||
fetchFavs(id) {
|
||||
return fetchFavoritedByUsers({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: favoritedByUsers }) =>
|
||||
this.addFavs({
|
||||
id,
|
||||
favoritedByUsers,
|
||||
}),
|
||||
)
|
||||
}).then((result) => {
|
||||
const users = useUsersStore().addNewUsers(result)
|
||||
return this.addFavs(id, new Set(users.map(({ id }) => id)))
|
||||
})
|
||||
},
|
||||
fetchRepeats(id) {
|
||||
return fetchRebloggedByUsers({
|
||||
id,
|
||||
credentials: useOAuthStore().token,
|
||||
}).then(({ data: rebloggedByUsers }) =>
|
||||
this.addRepeats({
|
||||
id,
|
||||
rebloggedByUsers,
|
||||
}),
|
||||
)
|
||||
}).then((result) => {
|
||||
const users = useUsersStore().addNewUsers(result)
|
||||
return this.addRepeats(id, new Set(users.map(({ id }) => id)))
|
||||
})
|
||||
},
|
||||
fetchFavsAndRepeats(id) {
|
||||
return Promise.all([this.fetchFavs(id), this.fetchRepeats(id)])
|
||||
},
|
||||
|
||||
// Updates
|
||||
addRepeats({ id, rebloggedByUsers }) {
|
||||
addRepeats(id, users) {
|
||||
const currentUser = useUsersStore().currentUser
|
||||
const newStatus = this.allStatuses.get(id)
|
||||
newStatus.rebloggedBy = rebloggedByUsers.filter(Boolean)
|
||||
// repeats stats can be incorrect based on polling condition, let's update them using the most recent data
|
||||
newStatus.repeat_num = newStatus.rebloggedBy.length
|
||||
newStatus.repeated = !!newStatus.rebloggedBy.find(
|
||||
({ id }) => currentUser?.id === id,
|
||||
)
|
||||
this.repeats.set(id, users)
|
||||
|
||||
// repeats stats can be incorrect based on polling
|
||||
// condition, let's update them using the most recent data
|
||||
newStatus.repeat_num = users.size
|
||||
newStatus.repeated = users.has(currentUser?.id)
|
||||
},
|
||||
addFavs({ id, favoritedByUsers }) {
|
||||
addFavs(id, users) {
|
||||
const currentUser = useUsersStore().currentUser
|
||||
const newStatus = this.allStatuses.get(id)
|
||||
newStatus.favoritedBy = favoritedByUsers.filter(Boolean)
|
||||
// favorites stats can be incorrect based on polling condition, let's update them using the most recent data
|
||||
newStatus.fave_num = newStatus.favoritedBy.length
|
||||
newStatus.favorited = !!newStatus.favoritedBy.find(
|
||||
({ id }) => currentUser?.id === id,
|
||||
)
|
||||
this.favs.set(id, users)
|
||||
|
||||
// favorites stats can be incorrect based on polling
|
||||
// condition, let's update them using the most recent data
|
||||
newStatus.fave_num = users.size
|
||||
newStatus.favorited = users.has(currentUser?.id)
|
||||
},
|
||||
addEmojiReactionsBy({ id, emojiReactions }) {
|
||||
addEmojiReactionsBy(id, emojiReactions) {
|
||||
const status = this.allStatuses.get(id)
|
||||
status.emoji_reactions = emojiReactions
|
||||
},
|
||||
|
|
@ -287,7 +297,7 @@ export const useStatusesStore = defineStore('statuses', {
|
|||
useInterfaceStore().pushGlobalNotice({
|
||||
level: 'error',
|
||||
messageKey: 'status.interact_error',
|
||||
messageArgs: [error],
|
||||
messageArgs: { error },
|
||||
timeout: 5000,
|
||||
})
|
||||
})
|
||||
|
|
@ -393,6 +403,7 @@ export const useStatusesStore = defineStore('statuses', {
|
|||
name: emoji,
|
||||
count: 0,
|
||||
accounts: [],
|
||||
account_ids: [],
|
||||
}
|
||||
|
||||
const count = value ? reaction.count + 1 : reaction.count - 1
|
||||
|
|
@ -400,12 +411,14 @@ export const useStatusesStore = defineStore('statuses', {
|
|||
const accounts = value
|
||||
? [...reaction.accounts, currentUser]
|
||||
: reaction.accounts.filter((acc) => acc.id !== currentUser.id)
|
||||
const account_ids = accounts.filter(Boolean).map(({ id }) => id)
|
||||
|
||||
const newReaction = {
|
||||
...reaction,
|
||||
count,
|
||||
me: value,
|
||||
accounts,
|
||||
account_ids,
|
||||
}
|
||||
|
||||
if (reactionPresent && count > 0) {
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ export const useStreamingStore = defineStore('streaming', {
|
|||
this.subscribers.delete(subscriber)
|
||||
if (stream) {
|
||||
this.subscriptions.get(stream.name).delete(stream.argument)
|
||||
} else {
|
||||
this.globalSubscriptions.delete(subscriber)
|
||||
}
|
||||
|
||||
if (stream && this.state === WSConnectionStatus.JOINED) {
|
||||
|
|
@ -106,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
|
||||
|
|
@ -127,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) {
|
||||
|
|
@ -227,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))
|
||||
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ export const ARGUMENT_MAP = {
|
|||
user: 'userId',
|
||||
userPinned: 'userId',
|
||||
media: 'userId',
|
||||
favorites: 'userId',
|
||||
}
|
||||
|
||||
const TIMELINES = new Set([
|
||||
|
|
@ -105,8 +106,6 @@ export const defaultState = () => {
|
|||
return Object.fromEntries([...TIMELINES].map((name) => [name, emptyTl(name)]))
|
||||
}
|
||||
|
||||
//const CUSTOM_SORT = new Set(['bookmarks', 'favorites'])
|
||||
|
||||
export const useTimelinesStore = defineStore('timelines', {
|
||||
state: defaultState,
|
||||
actions: {
|
||||
|
|
@ -182,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)
|
||||
|
|
@ -198,6 +197,7 @@ export const useTimelinesStore = defineStore('timelines', {
|
|||
timeline.maxId = ''
|
||||
timeline.minId = ''
|
||||
timeline.reloadNeeded = false
|
||||
timeline.fetcher.resetBottomedOut()
|
||||
},
|
||||
activatePersistents() {
|
||||
TIMELINES.forEach((name) => {
|
||||
|
|
@ -268,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
|
||||
|
|
@ -280,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) {
|
||||
|
|
@ -291,29 +287,39 @@ 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(timeline, argument, event) {
|
||||
this.addStatusesToTimeline(timeline, argument, {
|
||||
onStreamMessage(timelineName, argument, event) {
|
||||
this.addStatusesToTimeline(timelineName, argument, {
|
||||
statuses: event.data.map(({ id }) => id),
|
||||
repeats: event.data
|
||||
.filter(({ retweeted_status }) => Boolean(retweeted_status))
|
||||
|
|
@ -349,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
|
||||
|
|
@ -416,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) ?? ''
|
||||
|
|
@ -424,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
|
||||
}
|
||||
|
||||
|
|
@ -444,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))
|
||||
|
|
@ -453,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
|
||||
})
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -208,7 +208,12 @@ export const useUsersStore = defineStore('users', {
|
|||
// Misc updates
|
||||
updateUserAdminData(id, data) {
|
||||
const user = this.users.get(id)
|
||||
|
||||
if (!user) {
|
||||
console.warn(
|
||||
`User id ${id} somehow not found during admin data update!`,
|
||||
)
|
||||
return
|
||||
}
|
||||
user.adminData = data
|
||||
user.deactivated = !data.is_active
|
||||
user.tags = new Set(data.tags)
|
||||
|
|
@ -271,15 +276,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) {
|
||||
|
|
@ -513,7 +524,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,
|
||||
|
|
@ -532,7 +543,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,
|
||||
|
|
@ -549,7 +560,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],
|
||||
|
|
@ -718,6 +729,8 @@ export const useUsersStore = defineStore('users', {
|
|||
useAnnouncementsStore().stopFetching()
|
||||
useListsStore().stopFetching()
|
||||
useBookmarkFoldersStore().stopFetching()
|
||||
useChatsStore().stopFetching()
|
||||
|
||||
store?.dispatch('stopFetchingFollowRequests')
|
||||
|
||||
// NOTE: No need to verify the app still exists, because if it doesn't,
|
||||
|
|
@ -743,7 +756,6 @@ export const useUsersStore = defineStore('users', {
|
|||
// Full reset on logout success
|
||||
useTimelinesStore().deactivateAll()
|
||||
useStatusesStore().resetStatuses()
|
||||
useChatsStore().stopFetching()
|
||||
useChatsStore().resetChats()
|
||||
|
||||
this.users = new Map()
|
||||
|
|
|
|||
|
|
@ -290,7 +290,10 @@ describe('Statuses store', () => {
|
|||
'EmojiReactions',
|
||||
[
|
||||
{
|
||||
accounts: [mockMastoAPIUser()],
|
||||
accounts: [
|
||||
mockMastoAPIUser({ id: 'u1' }),
|
||||
mockMastoAPIUser({ id: 'u2' }),
|
||||
],
|
||||
count: 1,
|
||||
me: false,
|
||||
name: 'cofe',
|
||||
|
|
@ -298,8 +301,14 @@ describe('Statuses store', () => {
|
|||
},
|
||||
],
|
||||
],
|
||||
['Favs', [mockMastoAPIUser()]],
|
||||
['Repeats', [mockMastoAPIUser()]],
|
||||
[
|
||||
'Favs',
|
||||
[mockMastoAPIUser({ id: 'u1' }), mockMastoAPIUser({ id: 'u2' })],
|
||||
],
|
||||
[
|
||||
'Repeats',
|
||||
[mockMastoAPIUser({ id: 'u1' }), mockMastoAPIUser({ id: 'u2' })],
|
||||
],
|
||||
])('fetch%s', async (group, mockedResponse) => {
|
||||
const mockFetch = vi.fn()
|
||||
mockFetch.mockResolvedValueOnce(
|
||||
|
|
@ -309,6 +318,7 @@ describe('Statuses store', () => {
|
|||
)
|
||||
|
||||
vi.stubGlobal('fetch', mockFetch)
|
||||
const addNewUsers = vi.spyOn(useUsersStore(), 'addNewUsers')
|
||||
|
||||
let urlKey
|
||||
let prefix = 'MASTODON'
|
||||
|
|
@ -335,13 +345,29 @@ describe('Statuses store', () => {
|
|||
const result = await store[`fetch${group}`]('id')
|
||||
const updated = store.allStatuses.get('id')
|
||||
|
||||
// Fetch called
|
||||
expect(mockFetch).to.have.been.calledWith(url, DEFAULT_OPTIONS())
|
||||
|
||||
// Users updated
|
||||
if (group !== 'StatusSource') {
|
||||
// first call is the one for the status
|
||||
expect(addNewUsers).to.have.been.calledTwice
|
||||
const secondCallData = addNewUsers.mock.calls[1][0].data
|
||||
expect(secondCallData).to.have.length(2)
|
||||
expect(secondCallData[0]).to.have.property('id', 'u1')
|
||||
expect(secondCallData[1]).to.have.property('id', 'u2')
|
||||
}
|
||||
|
||||
if (group === 'Favs') {
|
||||
expect(updated.favoritedBy).to.have.length(1)
|
||||
expect(updated.fave_num).to.eql(1)
|
||||
expect(store.favs).to.have.length(1)
|
||||
expect(store.favs.get('id')).to.have.length(2)
|
||||
expect(store.favs.get('id')).to.eql(new Set(['u1', 'u2']))
|
||||
expect(updated.fave_num).to.eql(2)
|
||||
} else if (group === 'Repeats') {
|
||||
expect(updated.rebloggedBy).to.have.length(1)
|
||||
expect(updated.repeat_num).to.eql(1)
|
||||
expect(store.repeats).to.have.length(1)
|
||||
expect(store.repeats.get('id')).to.have.length(2)
|
||||
expect(store.repeats.get('id')).to.eql(new Set(['u1', 'u2']))
|
||||
expect(updated.repeat_num).to.eql(2)
|
||||
} else if (group === 'EmojiReactions') {
|
||||
expect(updated.emoji_reactions).to.have.length(mockedResponse.length)
|
||||
expect(updated.emoji_reactions[0].name).to.eql(mockedResponse[0].name)
|
||||
|
|
|
|||
|
|
@ -740,19 +740,24 @@ describe('Users store', () => {
|
|||
|
||||
const spies = [
|
||||
// Misc initialization
|
||||
vi.spyOn(useStatusesStore(), 'resetStatuses'),
|
||||
vi.spyOn(useInterfaceStore(), 'onLogout'),
|
||||
/* 0 */ vi.spyOn(useStatusesStore(), 'resetStatuses'),
|
||||
/* 1 */ vi.spyOn(useInterfaceStore(), 'onLogout'),
|
||||
|
||||
// Timeline / Notifications
|
||||
vi.spyOn(useNotificationsStore(), 'deactivate'),
|
||||
vi.spyOn(useTimelinesStore(), 'deactivateAll'),
|
||||
/* 2 */ vi.spyOn(useNotificationsStore(), 'deactivate'),
|
||||
/* 3 */ vi.spyOn(useNotificationsStore(), 'pause'),
|
||||
/* 4 */ vi.spyOn(useNotificationsStore(), 'resume'),
|
||||
/* 5 */ vi.spyOn(useTimelinesStore(), 'deactivateAll'),
|
||||
/* 6 */ vi.spyOn(useTimelinesStore(), 'pauseAll'),
|
||||
/* 7 */ vi.spyOn(useTimelinesStore(), 'resumeAll'),
|
||||
|
||||
// Fetchers
|
||||
vi.spyOn(useChatsStore(), 'resetChats'),
|
||||
vi.spyOn(useListsStore(), 'stopFetching'),
|
||||
vi.spyOn(useAnnouncementsStore(), 'stopFetching'),
|
||||
vi.spyOn(useBookmarkFoldersStore(), 'stopFetching'),
|
||||
vi.spyOn(useStreamingStore(), 'stopSocket'),
|
||||
/* 8 */ vi.spyOn(useChatsStore(), 'resetChats'),
|
||||
/* 9 */ vi.spyOn(useChatsStore(), 'stopFetching'),
|
||||
/* 10 */ vi.spyOn(useListsStore(), 'stopFetching'),
|
||||
/* 11 */ vi.spyOn(useAnnouncementsStore(), 'stopFetching'),
|
||||
/* 12 */ vi.spyOn(useBookmarkFoldersStore(), 'stopFetching'),
|
||||
/* 13 */ vi.spyOn(useStreamingStore(), 'stopSocket'),
|
||||
]
|
||||
|
||||
spies.forEach((spy) => {
|
||||
|
|
@ -791,6 +796,91 @@ describe('Users store', () => {
|
|||
expect(spy, `Spy ${index} has failed`).to.have.been.called
|
||||
})
|
||||
})
|
||||
|
||||
it('failed logout', async () => {
|
||||
const revokeApi = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
// Ensure APP
|
||||
new Response(JSON.stringify('ok'), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
// Revoke Token
|
||||
new Response(
|
||||
JSON.stringify('Oopsie-woopsie pleroma made a fucky-wucky'),
|
||||
{
|
||||
status: 500,
|
||||
statusText: 'Internal Server Error',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
vi.stubGlobal('fetch', revokeApi)
|
||||
|
||||
// NOTE: Order is not checked for!
|
||||
const spies = [
|
||||
// ## PAUSE ##
|
||||
// Timeline / Notifications
|
||||
/* 0 */ vi.spyOn(useTimelinesStore(), 'pauseAll'),
|
||||
/* 1 */ vi.spyOn(useNotificationsStore(), 'pause'),
|
||||
|
||||
// Fetchers (Pauseless)
|
||||
/* 2 */ vi.spyOn(useListsStore(), 'stopFetching'),
|
||||
/* 3 */ vi.spyOn(useChatsStore(), 'stopFetching'),
|
||||
/* 4 */ vi.spyOn(useAnnouncementsStore(), 'stopFetching'),
|
||||
/* 5 */ vi.spyOn(useBookmarkFoldersStore(), 'stopFetching'),
|
||||
|
||||
// ## RESUME ##
|
||||
// Timeline / Notifications
|
||||
/* 6 */ vi.spyOn(useNotificationsStore(), 'resume'),
|
||||
/* 7 */ vi.spyOn(useTimelinesStore(), 'resumeAll'),
|
||||
|
||||
// Fetchers (Pauseless)
|
||||
/* 8 */ vi.spyOn(useListsStore(), 'startFetching'),
|
||||
/* 9 */ vi.spyOn(useChatsStore(), 'startFetching'),
|
||||
/* 10 */ vi.spyOn(useAnnouncementsStore(), 'startFetching'),
|
||||
/* 11 */ vi.spyOn(useBookmarkFoldersStore(), 'startFetching'),
|
||||
]
|
||||
|
||||
spies.forEach((spy) => {
|
||||
spy.mockImplementation(async () => {
|
||||
/* no-op */
|
||||
})
|
||||
})
|
||||
|
||||
useInstanceCapabilitiesStore().pleromaChatMessagesAvailable = true
|
||||
useMergedConfigStore().mergedConfig = { useStreamingApi: true }
|
||||
|
||||
const store = useUsersStore()
|
||||
store.currentUser = mockUser()
|
||||
|
||||
// Adding some users to verify they are getting cleaned afterwards
|
||||
store.addNewUsers({
|
||||
data: [
|
||||
mockUser(),
|
||||
{
|
||||
...mockUser({ name: 'John', screen_name: 'snake' }),
|
||||
relationship: { id: userId, following: true },
|
||||
},
|
||||
{ ...mockUser({ name: 'David Oh', screen_name: 'zero' }) },
|
||||
],
|
||||
timestamp: 2000,
|
||||
})
|
||||
expect(store.loggedIn).to.eql(true)
|
||||
await store.logout()
|
||||
expect(store.loggedIn).to.eql(true)
|
||||
expect(revokeApi).to.have.been.called
|
||||
expect(store.users).to.have.length(1)
|
||||
expect(store.usersByName).to.have.length(1)
|
||||
expect(store.usersByURL).to.have.length(1)
|
||||
expect(store.relationships).to.have.length(1)
|
||||
spies.forEach((spy, index) => {
|
||||
expect(spy, `Spy ${index} has failed`).to.have.been.called
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue