Merge branch 'users-statuses-pinia' into shigusegubu-themes3
This commit is contained in:
commit
410e3cff41
55 changed files with 430 additions and 251 deletions
|
|
@ -90,7 +90,7 @@ const AccountActions = {
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
params: {
|
params: {
|
||||||
username: useUsersStore().currentUser.screen_name,
|
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 UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||||
|
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
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'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
const AvatarList = {
|
const AvatarList = {
|
||||||
props: ['users'],
|
props: {
|
||||||
|
userIds: Set,
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
slicedUsers() {
|
slicedUsers() {
|
||||||
return this.users ? this.users.slice(0, 15) : []
|
return [...(this.userIds ?? [])]
|
||||||
|
.slice(0, 15)
|
||||||
|
.map((id) => useUsersStore().findUser(id))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
{{ $t('admin_dash.users.labels.handle_colon') }}
|
{{ $t('admin_dash.users.labels.handle_colon') }}
|
||||||
{{ ' ' }}
|
{{ ' ' }}
|
||||||
</strong>
|
</strong>
|
||||||
<user-link
|
<UserLink
|
||||||
class="basic-user-card-screen-name"
|
class="basic-user-card-screen-name"
|
||||||
:user="user"
|
: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 UserAvatar from 'src/components/user_avatar/user_avatar.vue'
|
||||||
|
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
import { useSearchStore } from 'src/stores/search.js'
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { chats } from 'src/api/chats.js'
|
import { chats } from 'src/api/chats.js'
|
||||||
|
|
@ -50,7 +51,7 @@ const chatNew = {
|
||||||
this.$emit('cancel')
|
this.$emit('cancel')
|
||||||
},
|
},
|
||||||
goToChat(user) {
|
goToChat(user) {
|
||||||
this.$router.push({ name: 'chat', params: { recipient_id: user.id } })
|
this.$router.push({ name: 'chat', params: { chatUserId: user.id } })
|
||||||
},
|
},
|
||||||
onInput() {
|
onInput() {
|
||||||
this.search(this.query)
|
this.search(this.query)
|
||||||
|
|
@ -71,7 +72,7 @@ const chatNew = {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.userIds = []
|
this.userIds = []
|
||||||
this.$store
|
this.$store
|
||||||
this.useSearchStore()
|
useSearchStore()
|
||||||
.search({ q: query, resolve: true, type: 'accounts' })
|
.search({ q: query, resolve: true, type: 'accounts' })
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
|
|
||||||
|
|
@ -472,8 +472,8 @@ const Chat = {
|
||||||
|
|
||||||
// Clear any known pending messages
|
// Clear any known pending messages
|
||||||
if (message.idempotency_key) {
|
if (message.idempotency_key) {
|
||||||
if (this.pendingMessagesIndex[message.idempotencyKeyIndex]) {
|
if (this.pendingMessagesIndex[message.idempotency_key]) {
|
||||||
delete this.pendingMessagesIndex[message.idempotencyKeyIndex]
|
delete this.pendingMessagesIndex[message.idempotency_key]
|
||||||
this.pendingMessages = this.pendingMessages.filter(
|
this.pendingMessages = this.pendingMessages.filter(
|
||||||
({ idempotency_key }) =>
|
({ idempotency_key }) =>
|
||||||
idempotency_key !== message.idempotency_key,
|
idempotency_key !== message.idempotency_key,
|
||||||
|
|
|
||||||
|
|
@ -185,6 +185,7 @@ const conversation = {
|
||||||
|
|
||||||
return [...conversation.keys()]
|
return [...conversation.keys()]
|
||||||
.map((k) => useStatusesStore().allStatuses.get(k))
|
.map((k) => useStatusesStore().allStatuses.get(k))
|
||||||
|
.filter((status) => status.type != 'repeat') // Old backend behavior?
|
||||||
.toSorted(sortById)
|
.toSorted(sortById)
|
||||||
},
|
},
|
||||||
statusMap() {
|
statusMap() {
|
||||||
|
|
@ -621,6 +622,7 @@ const conversation = {
|
||||||
},
|
},
|
||||||
updateVirtualHeight() {
|
updateVirtualHeight() {
|
||||||
if (this.hide) return // no updates when not rendering
|
if (this.hide) return // no updates when not rendering
|
||||||
|
if (!this.status) return // not loaded yet
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.virtualHeight = this.$refs.body.getBoundingClientRect().height
|
this.virtualHeight = this.$refs.body.getBoundingClientRect().height
|
||||||
this.$emit('update:virtualHeight', {
|
this.$emit('update:virtualHeight', {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
import Modal from 'src/components/modal/modal.vue'
|
import Modal from 'src/components/modal/modal.vue'
|
||||||
|
|
@ -19,18 +20,16 @@ const EditStatusModal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isLoggedIn() {
|
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
modalActivated() {
|
modalActivated() {
|
||||||
return useEditStatusStore().modalActivated
|
return useEditStatusStore().modalActivated
|
||||||
},
|
},
|
||||||
isFormVisible() {
|
isFormVisible() {
|
||||||
return this.isLoggedIn && !this.resettingForm && this.modalActivated
|
return this.loggedIn && !this.resettingForm && this.modalActivated
|
||||||
},
|
},
|
||||||
params() {
|
params() {
|
||||||
return useEditStatusStore().params || {}
|
return useEditStatusStore().params || {}
|
||||||
},
|
},
|
||||||
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
params(newVal, oldVal) {
|
params(newVal, oldVal) {
|
||||||
|
|
|
||||||
|
|
@ -37,9 +37,9 @@ const EmojiReactions = {
|
||||||
},
|
},
|
||||||
accountsForEmoji() {
|
accountsForEmoji() {
|
||||||
return this.status.emoji_reactions.reduce((acc, reaction) => {
|
return this.status.emoji_reactions.reduce((acc, reaction) => {
|
||||||
acc[reaction.name] = reaction.accounts || []
|
acc.set(reaction.name, new Set(reaction.account_ids))
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, new Map())
|
||||||
},
|
},
|
||||||
loggedIn() {
|
loggedIn() {
|
||||||
return !!useUsersStore().currentUser
|
return !!useUsersStore().currentUser
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
</FALayers>
|
</FALayers>
|
||||||
</component>
|
</component>
|
||||||
<UserListPopover
|
<UserListPopover
|
||||||
:users="accountsForEmoji[reaction.name]"
|
:user-ids="accountsForEmoji.get(reaction.name)"
|
||||||
class="emoji-reaction-popover"
|
class="emoji-reaction-popover"
|
||||||
:normal-button="true"
|
:normal-button="true"
|
||||||
:trigger-attrs="counterTriggerAttrs(reaction)"
|
:trigger-attrs="counterTriggerAttrs(reaction)"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const FollowCard = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isMe() {
|
isMe() {
|
||||||
return useUsersStore().currentUser.id === this.user.id
|
return useUsersStore().currentUser?.id === this.user.id
|
||||||
},
|
},
|
||||||
loggedIn() {
|
loggedIn() {
|
||||||
return useUsersStore().currentUser
|
return useUsersStore().currentUser
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { debounce } from 'lodash'
|
||||||
|
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
||||||
|
import { useSearchStore } from 'src/stores/search.js'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
|
import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
|
@ -32,7 +34,7 @@ const ListsUserSearch = {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.$emit('loading')
|
this.$emit('loading')
|
||||||
this.userIds = []
|
this.userIds = []
|
||||||
this.useSearchStore()
|
useSearchStore()
|
||||||
.search({
|
.search({
|
||||||
q: query,
|
q: query,
|
||||||
resolve: true,
|
resolve: true,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
import { usePostStatusStore } from 'src/stores/post_status.js'
|
import { usePostStatusStore } from 'src/stores/post_status.js'
|
||||||
|
|
@ -34,9 +35,6 @@ const MobilePostStatusButton = {
|
||||||
window.removeEventListener('resize', this.handleOSK)
|
window.removeEventListener('resize', this.handleOSK)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isLoggedIn() {
|
|
||||||
return useUsersStore().loggedIn
|
|
||||||
},
|
|
||||||
isHidden() {
|
isHidden() {
|
||||||
if (HIDDEN_FOR_PAGES.has(this.$route.name)) {
|
if (HIDDEN_FOR_PAGES.has(this.$route.name)) {
|
||||||
return true
|
return true
|
||||||
|
|
@ -52,6 +50,7 @@ const MobilePostStatusButton = {
|
||||||
autohideFloatingPostButton() {
|
autohideFloatingPostButton() {
|
||||||
return !!useMergedConfigStore().mergedConfig.autohideFloatingPostButton
|
return !!useMergedConfigStore().mergedConfig.autohideFloatingPostButton
|
||||||
},
|
},
|
||||||
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
autohideFloatingPostButton: function (isEnabled) {
|
autohideFloatingPostButton: function (isEnabled) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
class="MobilePostButton button-default new-status-button"
|
class="MobilePostButton button-default new-status-button"
|
||||||
:class="{ 'hidden': isHidden, 'always-show': isPersistent }"
|
:class="{ 'hidden': isHidden, 'always-show': isPersistent }"
|
||||||
:title="$t('post_status.new_status')"
|
:title="$t('post_status.new_status')"
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
class="Notification container -muted"
|
class="Notification container -muted"
|
||||||
>
|
>
|
||||||
<small>
|
<small>
|
||||||
<user-link
|
<UserLink
|
||||||
:user="notification.from_profile"
|
:user="notification.from_profile"
|
||||||
:at="false"
|
:at="false"
|
||||||
/>
|
/>
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
|
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
|
||||||
class="follow-text"
|
class="follow-text"
|
||||||
>
|
>
|
||||||
<user-link
|
<UserLink
|
||||||
class="follow-name"
|
class="follow-name"
|
||||||
:user="notification.from_profile"
|
:user="notification.from_profile"
|
||||||
/>
|
/>
|
||||||
|
|
@ -249,7 +249,7 @@
|
||||||
v-else-if="notification.type === 'move'"
|
v-else-if="notification.type === 'move'"
|
||||||
class="move-text"
|
class="move-text"
|
||||||
>
|
>
|
||||||
<user-link
|
<UserLink
|
||||||
:user="notification.target"
|
:user="notification.target"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { get } from 'lodash'
|
import { get } from 'lodash'
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
|
||||||
import Modal from 'src/components/modal/modal.vue'
|
import Modal from 'src/components/modal/modal.vue'
|
||||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||||
|
|
@ -17,18 +18,16 @@ const PostStatusModal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isLoggedIn() {
|
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
modalActivated() {
|
modalActivated() {
|
||||||
return usePostStatusStore().modalActivated
|
return usePostStatusStore().modalActivated
|
||||||
},
|
},
|
||||||
isFormVisible() {
|
isFormVisible() {
|
||||||
return this.isLoggedIn && !this.resettingForm && this.modalActivated
|
return this.loggedIn && !this.resettingForm && this.modalActivated
|
||||||
},
|
},
|
||||||
params() {
|
params() {
|
||||||
return usePostStatusStore().params || {}
|
return usePostStatusStore().params || {}
|
||||||
},
|
},
|
||||||
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
params(newVal, oldVal) {
|
params(newVal, oldVal) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<Modal
|
<Modal
|
||||||
v-if="isLoggedIn && !resettingForm"
|
v-if="loggedIn && !resettingForm"
|
||||||
:is-open="modalActivated"
|
:is-open="modalActivated"
|
||||||
class="post-form-modal-view"
|
class="post-form-modal-view"
|
||||||
@backdrop-clicked="closeModal"
|
@backdrop-clicked="closeModal"
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import Popover from 'src/components/popover/popover.vue'
|
||||||
import { useInterfaceStore } from 'src/stores/interface.js'
|
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||||
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
import { useLocalConfigStore } from 'src/stores/local_config.js'
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_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 { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
|
@ -28,13 +28,14 @@ const QuickFilterSettings = {
|
||||||
path: 'replyVisibility',
|
path: 'replyVisibility',
|
||||||
value: visibility,
|
value: visibility,
|
||||||
})
|
})
|
||||||
useStatusesStore().requireReloadAll()
|
useTimelinesStore().requireReloadAll()
|
||||||
},
|
},
|
||||||
openTab(tab) {
|
openTab(tab) {
|
||||||
useInterfaceStore().openSettingsModalTab(tab)
|
useInterfaceStore().openSettingsModalTab(tab)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
...mapState(useMergedConfigStore, ['mergedConfig']),
|
...mapState(useMergedConfigStore, ['mergedConfig']),
|
||||||
...mapState(useInterfaceStore, {
|
...mapState(useInterfaceStore, {
|
||||||
mobileLayout: (state) => state.layoutType === 'mobile',
|
mobileLayout: (state) => state.layoutType === 'mobile',
|
||||||
|
|
@ -55,9 +56,6 @@ const QuickFilterSettings = {
|
||||||
return 'dropdown-item'
|
return 'dropdown-item'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loggedIn() {
|
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
replyVisibilitySelf: {
|
replyVisibilitySelf: {
|
||||||
get() {
|
get() {
|
||||||
return this.mergedConfig.replyVisibility === 'self'
|
return this.mergedConfig.replyVisibility === 'self'
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,7 @@ const QuickViewSettings = {
|
||||||
...mapState(useInterfaceStore, {
|
...mapState(useInterfaceStore, {
|
||||||
mobileLayout: (state) => state.layoutType === 'mobile',
|
mobileLayout: (state) => state.layoutType === 'mobile',
|
||||||
}),
|
}),
|
||||||
loggedIn() {
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
conversationDisplay: {
|
conversationDisplay: {
|
||||||
get() {
|
get() {
|
||||||
return this.mergedConfig.conversationDisplay
|
return this.mergedConfig.conversationDisplay
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
import Quote from './quote.vue'
|
import Quote from './quote.vue'
|
||||||
|
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useSearchStore } from 'src/stores/search.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -93,7 +94,7 @@ export default {
|
||||||
this.$emit('update:id', notice[3])
|
this.$emit('update:id', notice[3])
|
||||||
} else if (value) {
|
} else if (value) {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.useSearchStore()
|
useSearchStore()
|
||||||
.search({
|
.search({
|
||||||
q: value,
|
q: value,
|
||||||
resolve: true,
|
resolve: true,
|
||||||
|
|
|
||||||
|
|
@ -100,9 +100,6 @@ const SettingsModalAdminContent = {
|
||||||
user() {
|
user() {
|
||||||
return useUsersStore().currentUser
|
return useUsersStore().currentUser
|
||||||
},
|
},
|
||||||
isLoggedIn() {
|
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
open() {
|
open() {
|
||||||
return useInterfaceStore().settingsModalState !== 'hidden'
|
return useInterfaceStore().settingsModalState !== 'hidden'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { mapState } from 'pinia'
|
||||||
|
|
||||||
import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
|
import VerticalTabSwitcher from './helpers/vertical_tab_switcher.jsx'
|
||||||
import AppearanceTab from './tabs/appearance_tab.vue'
|
import AppearanceTab from './tabs/appearance_tab.vue'
|
||||||
import ClutterTab from './tabs/clutter_tab.vue'
|
import ClutterTab from './tabs/clutter_tab.vue'
|
||||||
|
|
@ -75,9 +77,6 @@ const SettingsModalContent = {
|
||||||
OldThemeTab,
|
OldThemeTab,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isLoggedIn() {
|
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
open() {
|
open() {
|
||||||
return useInterfaceStore().settingsModalState !== 'hidden'
|
return useInterfaceStore().settingsModalState !== 'hidden'
|
||||||
},
|
},
|
||||||
|
|
@ -87,6 +86,7 @@ const SettingsModalContent = {
|
||||||
expertLevel() {
|
expertLevel() {
|
||||||
return useMergedConfigStore().mergedConfig.expertLevel
|
return useMergedConfigStore().mergedConfig.expertLevel
|
||||||
},
|
},
|
||||||
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<GeneralTab />
|
<GeneralTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
:label="$t('settings.profile_tab')"
|
:label="$t('settings.profile_tab')"
|
||||||
icon="user"
|
icon="user"
|
||||||
data-tab-name="profile"
|
data-tab-name="profile"
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<ProfileTab />
|
<ProfileTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
:label="$t('settings.composing')"
|
:label="$t('settings.composing')"
|
||||||
icon="pen-alt"
|
icon="pen-alt"
|
||||||
data-tab-name="composing"
|
data-tab-name="composing"
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
<LayoutTab />
|
<LayoutTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
:full-width="true"
|
:full-width="true"
|
||||||
:label="$t('settings.notifications')"
|
:label="$t('settings.notifications')"
|
||||||
icon="bell"
|
icon="bell"
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
<FilteringTab />
|
<FilteringTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
:label="$t('settings.mutes_and_blocks')"
|
:label="$t('settings.mutes_and_blocks')"
|
||||||
icon="eye-slash"
|
icon="eye-slash"
|
||||||
data-tab-name="mutesAndBlocks"
|
data-tab-name="mutesAndBlocks"
|
||||||
|
|
@ -90,7 +90,7 @@
|
||||||
<ClutterTab />
|
<ClutterTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
:label="$t('settings.security_tab')"
|
:label="$t('settings.security_tab')"
|
||||||
icon="lock"
|
icon="lock"
|
||||||
data-tab-name="security"
|
data-tab-name="security"
|
||||||
|
|
@ -98,7 +98,7 @@
|
||||||
<SecurityTab />
|
<SecurityTab />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isLoggedIn"
|
v-if="loggedIn"
|
||||||
:label="$t('settings.data_import_export_tab')"
|
:label="$t('settings.data_import_export_tab')"
|
||||||
icon="download"
|
icon="download"
|
||||||
data-tab-name="dataImportExport"
|
data-tab-name="dataImportExport"
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ const AppearanceTab = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isDefaultBackground() {
|
isDefaultBackground() {
|
||||||
return !useUsersStore().currentUser.background_image
|
return !useUsersStore().currentUser?.background_image
|
||||||
},
|
},
|
||||||
switchInProgress() {
|
switchInProgress() {
|
||||||
return useInterfaceStore().themeChangeInProgress
|
return useInterfaceStore().themeChangeInProgress
|
||||||
|
|
@ -283,7 +283,7 @@ const AppearanceTab = {
|
||||||
instanceWallpaperUsed() {
|
instanceWallpaperUsed() {
|
||||||
return (
|
return (
|
||||||
useInstanceStore().instanceIdentity.background &&
|
useInstanceStore().instanceIdentity.background &&
|
||||||
!useUsersStore().currentUser.background_image
|
!useUsersStore().currentUser?.background_image
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
customThemeVersion() {
|
customThemeVersion() {
|
||||||
|
|
|
||||||
|
|
@ -162,9 +162,9 @@
|
||||||
<div class="fun-monitor-display-bezel button-default">
|
<div class="fun-monitor-display-bezel button-default">
|
||||||
<div class="fun-monitor-display-screen input">
|
<div class="fun-monitor-display-screen input">
|
||||||
<img
|
<img
|
||||||
v-if="backgroundPreview || user.background_image || instanceWallpaper"
|
v-if="backgroundPreview || user?.background_image || instanceWallpaper"
|
||||||
class="fun-monitor-display-screen-image"
|
class="fun-monitor-display-screen-image"
|
||||||
:src="backgroundPreview || user.background_image || instanceWallpaper"
|
:src="backgroundPreview || user?.background_image || instanceWallpaper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import UnitSetting from '../helpers/unit_setting.vue'
|
||||||
|
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.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 = {
|
const ClutterTab = {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -36,7 +36,7 @@ const ClutterTab = {
|
||||||
// Updating nested properties
|
// Updating nested properties
|
||||||
watch: {
|
watch: {
|
||||||
replyVisibility() {
|
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 { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
import { useInterfaceStore } from 'src/stores/interface'
|
import { useInterfaceStore } from 'src/stores/interface'
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_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 { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
import { useTimelinesStore } from 'src/stores/timelines.js'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
newExporter,
|
newExporter,
|
||||||
|
|
@ -266,7 +266,7 @@ const FilteringTab = {
|
||||||
// Updating nested properties
|
// Updating nested properties
|
||||||
watch: {
|
watch: {
|
||||||
replyVisibility() {
|
replyVisibility() {
|
||||||
useStatusesStore().requireReloadAll()
|
useTimelinesStore().requireReloadAll()
|
||||||
},
|
},
|
||||||
muteFiltersObject() {
|
muteFiltersObject() {
|
||||||
this.muteFiltersDraftObject = cloneDeep(
|
this.muteFiltersDraftObject = cloneDeep(
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ const GeneralTab = {
|
||||||
value: mode,
|
value: mode,
|
||||||
label: this.$t(`settings.absolute_time_format_12h_${mode}`),
|
label: this.$t(`settings.absolute_time_format_12h_${mode}`),
|
||||||
})),
|
})),
|
||||||
emailLanguage: useUsersStore().currentUser.language || [''],
|
emailLanguage: useUsersStore().currentUser?.language || [''],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -72,6 +72,9 @@ const GeneralTab = {
|
||||||
useLocalConfigStore().set({ path, value })
|
useLocalConfigStore().set({ path, value })
|
||||||
},
|
},
|
||||||
toggleStreaming(value) {
|
toggleStreaming(value) {
|
||||||
|
// Streaming is not available for the unauthenticated
|
||||||
|
if (!useOAuthStore().token) return
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
useStreamingStore().initSocket()
|
useStreamingStore().initSocket()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { uniqBy } from 'lodash'
|
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
|
||||||
import AvatarList from 'src/components/avatar_list/avatar_list.vue'
|
import AvatarList from 'src/components/avatar_list/avatar_list.vue'
|
||||||
|
|
@ -137,13 +136,30 @@ const Status = {
|
||||||
useScrobblesStore().getLatestScrobble(this.status.user.id)
|
useScrobblesStore().getLatestScrobble(this.status.user.id)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
// Whatever we're given to work with
|
||||||
status() {
|
status() {
|
||||||
return this.statusoid ?? useStatusesStore().allStatuses.get(this.statusId)
|
return this.statusoid ?? useStatusesStore().allStatuses.get(this.statusId)
|
||||||
},
|
},
|
||||||
|
// Status repeated
|
||||||
repeatedStatus() {
|
repeatedStatus() {
|
||||||
if (this.status.retweeted_status === undefined) return undefined
|
if (this.status.retweeted_status === undefined) return undefined
|
||||||
return useStatusesStore().allStatuses.get(this.status.retweeted_status.id)
|
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() {
|
repeater() {
|
||||||
return useUsersStore().findUser(this.status.user.id)
|
return useUsersStore().findUser(this.status.user.id)
|
||||||
},
|
},
|
||||||
|
|
@ -152,7 +168,7 @@ const Status = {
|
||||||
},
|
},
|
||||||
showReasonMutedThread() {
|
showReasonMutedThread() {
|
||||||
return (
|
return (
|
||||||
(this.mainStatus.thread_muted || this.mainSatus.reblog?.thread_muted) &&
|
(this.mainStatus.thread_muted || this.repeatStatus?.thread_muted) &&
|
||||||
!this.inConversation
|
!this.inConversation
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -179,6 +195,12 @@ const Status = {
|
||||||
useUserHighlightStore().get(this.repeater.screen_name),
|
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() {
|
userStyle() {
|
||||||
if (this.noHeading) return
|
if (this.noHeading) return
|
||||||
return highlightStyle(useUserHighlightStore().get(this.user.screen_name))
|
return highlightStyle(useUserHighlightStore().get(this.user.screen_name))
|
||||||
|
|
@ -212,13 +234,6 @@ const Status = {
|
||||||
this.repeater.screen_name,
|
this.repeater.screen_name,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
mainStatus() {
|
|
||||||
if (this.isRepeat) {
|
|
||||||
return this.repeatedStatus
|
|
||||||
} else {
|
|
||||||
return this.status
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loggedIn() {
|
loggedIn() {
|
||||||
return !!this.currentUser
|
return !!this.currentUser
|
||||||
},
|
},
|
||||||
|
|
@ -383,12 +398,7 @@ const Status = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
combinedFavsAndRepeatsUsers() {
|
combinedFavsAndRepeatsUsers() {
|
||||||
// Use the status from the global status repository since favs and repeats are saved in it
|
return new Set([...this.favoritedBy, ...this.repeatedBy])
|
||||||
const combinedUsers = [].concat(
|
|
||||||
this.mainStatus.favoritedBy,
|
|
||||||
this.mainStatus.rebloggedBy,
|
|
||||||
)
|
|
||||||
return uniqBy(combinedUsers, 'id')
|
|
||||||
},
|
},
|
||||||
tags() {
|
tags() {
|
||||||
return [...this.status.tags]
|
return [...this.status.tags]
|
||||||
|
|
@ -403,7 +413,7 @@ const Status = {
|
||||||
return (
|
return (
|
||||||
!this.hidePostStats &&
|
!this.hidePostStats &&
|
||||||
this.focused &&
|
this.focused &&
|
||||||
(this.combinedFavsAndRepeatsUsers.length > 0 ||
|
(this.combinedFavsAndRepeatsUsers.size > 0 ||
|
||||||
this.mainStatus.quotes_count)
|
this.mainStatus.quotes_count)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -586,22 +596,14 @@ const Status = {
|
||||||
},
|
},
|
||||||
'mainStatus.repeat_num': function (num) {
|
'mainStatus.repeat_num': function (num) {
|
||||||
// refetch repeats when repeat_num is changed in any way
|
// refetch repeats when repeat_num is changed in any way
|
||||||
if (
|
if (this.focused && this.repeatedBy.size !== num) {
|
||||||
this.focused &&
|
useStatusesStore().fetchRepeats(this.mainStatus.id)
|
||||||
this.mainStatus.rebloggedBy &&
|
|
||||||
this.mainStatus.rebloggedBy.length !== num
|
|
||||||
) {
|
|
||||||
useStatusesStore().fetchRepeats(this.status.id)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'mainStatus.fave_num': function (num) {
|
'mainStatus.fave_num': function (num) {
|
||||||
// refetch favs when fave_num is changed in any way
|
// refetch favs when fave_num is changed in any way
|
||||||
if (
|
if (this.focused && this.favoritedBy.size !== num) {
|
||||||
this.focused &&
|
useStatusesStore().fetchFavs(this.mainStatus.id)
|
||||||
this.mainStatus.favoritedBy &&
|
|
||||||
this.mainStatus.favoritedBy.length !== num
|
|
||||||
) {
|
|
||||||
useStatusesStore().fetchFavs(this.status.id)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSuspendable: function (suspend) {
|
isSuspendable: function (suspend) {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
class="fa-scale-110 fa-old-padding repeat-icon"
|
class="fa-scale-110 fa-old-padding repeat-icon"
|
||||||
icon="retweet"
|
icon="retweet"
|
||||||
/>
|
/>
|
||||||
<user-link
|
<UserLink
|
||||||
:user="repeater"
|
:user="repeater"
|
||||||
:at="false"
|
:at="false"
|
||||||
/>
|
/>
|
||||||
|
|
@ -154,7 +154,7 @@
|
||||||
>
|
>
|
||||||
{{ user.name }}
|
{{ user.name }}
|
||||||
</h4>
|
</h4>
|
||||||
<user-link
|
<UserLink
|
||||||
class="account-name"
|
class="account-name"
|
||||||
:title="user.screen_name_ui"
|
:title="user.screen_name_ui"
|
||||||
:user="user"
|
:user="user"
|
||||||
|
|
@ -464,26 +464,26 @@
|
||||||
>
|
>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<UserListPopover
|
<UserListPopover
|
||||||
v-if="mainStatus.rebloggedBy && mainStatus.rebloggedBy.length > 0"
|
v-if="repeatedBy.size > 0"
|
||||||
:users="mainStatus.rebloggedBy"
|
:user-ids="repeatedBy"
|
||||||
>
|
>
|
||||||
<div class="stat-count">
|
<div class="stat-count">
|
||||||
<a class="stat-title">{{ $t('status.repeats') }}</a>
|
<a class="stat-title">{{ $t('status.repeats') }}</a>
|
||||||
<div class="stat-number">
|
<div class="stat-number">
|
||||||
{{ mainStatus.rebloggedBy.length }}
|
{{ repeatedBy.size }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UserListPopover>
|
</UserListPopover>
|
||||||
<UserListPopover
|
<UserListPopover
|
||||||
v-if="mainStatus.favoritedBy && mainStatus.favoritedBy.length > 0"
|
v-if="favoritedBy.size > 0"
|
||||||
:users="mainStatus.favoritedBy"
|
:user-ids="favoritedBy"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="stat-count"
|
class="stat-count"
|
||||||
>
|
>
|
||||||
<a class="stat-title">{{ $t('status.favorites') }}</a>
|
<a class="stat-title">{{ $t('status.favorites') }}</a>
|
||||||
<div class="stat-number">
|
<div class="stat-number">
|
||||||
{{ mainStatus.favoritedBy.length }}
|
{{ favoritedBy.size }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</UserListPopover>
|
</UserListPopover>
|
||||||
|
|
@ -501,7 +501,7 @@
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="avatar-row">
|
<div class="avatar-row">
|
||||||
<AvatarList :users="combinedFavsAndRepeatsUsers" />
|
<AvatarList :user-ids="combinedFavsAndRepeatsUsers" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,10 @@ const Timeline = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
statusesToDisplay() {
|
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 amount = this.timeline.visibleStatusIds.size
|
||||||
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
|
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
|
||||||
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
|
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
|
||||||
|
|
@ -214,6 +217,7 @@ const Timeline = {
|
||||||
let err = statuses[approxIndex].getBoundingClientRect().y
|
let err = statuses[approxIndex].getBoundingClientRect().y
|
||||||
|
|
||||||
// if we have a previous scroll index that can be used, test if it's
|
// if we have a previous scroll index that can be used, test if it's
|
||||||
|
// closer than the previous approximation, use it if so
|
||||||
|
|
||||||
const virtualScrollIndexY =
|
const virtualScrollIndexY =
|
||||||
statuses[cappedScrollIndex].getBoundingClientRect().y
|
statuses[cappedScrollIndex].getBoundingClientRect().y
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
:timeline-name="timelineRef.name"
|
:timeline-name="timelineRef.name"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="timeline.fetcher.loadingNewer"
|
v-if="timeline.fetcher.loadingNewer && !showLoadButton"
|
||||||
class="loadingIndicator"
|
class="loadingIndicator"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,22 @@ const TimelineMenu = {
|
||||||
(route === 'bookmark-folder' || route === 'bookmarks')
|
(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, [
|
...mapState(useInstanceCapabilitiesStore, [
|
||||||
'pleromaChatMessagesAvailable',
|
'pleromaChatMessagesAvailable',
|
||||||
'pleromaBookmarkFoldersAvailable',
|
'pleromaBookmarkFoldersAvailable',
|
||||||
|
|
@ -103,22 +119,6 @@ const TimelineMenu = {
|
||||||
event.stopPropagation()
|
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>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<span class="button-unstyled timeline-menu-title">
|
<span class="button-unstyled timeline-menu-title">
|
||||||
<h1 class="title timeline-title">{{ timelineName() }}</h1>
|
<h1 class="title timeline-title">{{ timelineName }}</h1>
|
||||||
<span>
|
<span>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ const UserAvatar = {
|
||||||
return useUsersStore().findUser(this.userId)
|
return useUsersStore().findUser(this.userId)
|
||||||
},
|
},
|
||||||
showActorTypeIndicator() {
|
showActorTypeIndicator() {
|
||||||
return useMergedConfigStore().mergedConfig.hideBotIndication
|
return !useMergedConfigStore().mergedConfig.hideBotIndication
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ export default {
|
||||||
return useUsersStore().relationship(this.userId)
|
return useUsersStore().relationship(this.userId)
|
||||||
},
|
},
|
||||||
isOtherUser() {
|
isOtherUser() {
|
||||||
return this.user.id !== useUsersStore().currentUser.id
|
return this.user.id !== useUsersStore().currentUser?.id
|
||||||
},
|
},
|
||||||
subscribeUrl() {
|
subscribeUrl() {
|
||||||
const serverUrl = new URL(this.user.statusnet_profile_url)
|
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 { useInstanceStore } from 'src/stores/instance.js'
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.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'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
|
|
@ -14,15 +15,22 @@ library.add(faCircleNotch)
|
||||||
|
|
||||||
const UserListPopover = {
|
const UserListPopover = {
|
||||||
name: 'UserListPopover',
|
name: 'UserListPopover',
|
||||||
props: ['users'],
|
props: {
|
||||||
|
userIds: Set,
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
UnicodeDomainIndicator,
|
UnicodeDomainIndicator,
|
||||||
Popover,
|
Popover,
|
||||||
UserAvatar,
|
UserAvatar,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
users() {
|
||||||
|
return [...this.userIds]
|
||||||
|
.map((id) => useUsersStore().findUser(id))
|
||||||
|
.filter(Boolean)
|
||||||
|
},
|
||||||
usersCapped() {
|
usersCapped() {
|
||||||
return this.users.slice(0, 16)
|
return [...this.users].slice(0, 16)
|
||||||
},
|
},
|
||||||
allowNonSquareEmoji() {
|
allowNonSquareEmoji() {
|
||||||
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
return useMergedConfigStore().mergedConfig.nonSquareEmoji
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="user-list-popover">
|
<div class="user-list-popover">
|
||||||
<template v-if="users.length">
|
<template v-if="userIds.size > 0">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="(user) in usersCapped"
|
v-for="(user) in usersCapped"
|
||||||
:key="user.id"
|
:key="user.id"
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,7 @@ const UserProfile = {
|
||||||
return useTimelinesStore().media
|
return useTimelinesStore().media
|
||||||
},
|
},
|
||||||
isUs() {
|
isUs() {
|
||||||
return (
|
return this.userId && this.userId === useUsersStore().currentUser?.id
|
||||||
this.userId &&
|
|
||||||
useUsersStore().currentUser.id &&
|
|
||||||
this.userId === useUsersStore().currentUser.id
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
user() {
|
user() {
|
||||||
return useUsersStore().findUser(this.userId)
|
return useUsersStore().findUser(this.userId)
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,8 @@ const UserReportingModal = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isLoggedIn() {
|
|
||||||
return !!useUsersStore().currentUser
|
|
||||||
},
|
|
||||||
isOpen() {
|
isOpen() {
|
||||||
return this.isLoggedIn && this.reportModal.activated
|
return this.loggedIn && this.reportModal.activated
|
||||||
},
|
},
|
||||||
userId() {
|
userId() {
|
||||||
return this.reportModal.userId
|
return this.reportModal.userId
|
||||||
|
|
@ -47,6 +44,7 @@ const UserReportingModal = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
...mapState(useReportsStore, ['reportModal']),
|
...mapState(useReportsStore, ['reportModal']),
|
||||||
|
...mapState(useUsersStore, ['loggedIn']),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
userId: 'resetState',
|
userId: 'resetState',
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,9 @@ const WhoToFollow = {
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then((result) => {
|
}).then((result) => {
|
||||||
const { data: externalUser } = result
|
const [user] = useUsersStore().addNewUsers(result)
|
||||||
if (!externalUser.error) {
|
|
||||||
useUsersStore().addNewUsers(result)
|
this.users.push(user)
|
||||||
this.users.push(externalUser)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1634,7 +1634,6 @@
|
||||||
"no_statuses": "No statuses",
|
"no_statuses": "No statuses",
|
||||||
"socket_reconnected": "Realtime connection established",
|
"socket_reconnected": "Realtime connection established",
|
||||||
"socket_disconnected": "Realtime connection unavaialable",
|
"socket_disconnected": "Realtime connection unavaialable",
|
||||||
"socket_closed": "Realtime connection closed",
|
|
||||||
"socket_broke": "Realtime connection lost: CloseEvent code {0}",
|
"socket_broke": "Realtime connection lost: CloseEvent code {0}",
|
||||||
"quick_view_settings": "Quick view settings",
|
"quick_view_settings": "Quick view settings",
|
||||||
"quick_filter_settings": "Quick filter settings",
|
"quick_filter_settings": "Quick filter settings",
|
||||||
|
|
|
||||||
|
|
@ -385,10 +385,13 @@ export const parseLinkHeaderPagination = (linkHeader, opts = {}) => {
|
||||||
const maxId = parsedLinkHeader.next?.max_id
|
const maxId = parsedLinkHeader.next?.max_id
|
||||||
const minId = parsedLinkHeader.prev?.min_id
|
const minId = parsedLinkHeader.prev?.min_id
|
||||||
|
|
||||||
return {
|
const result = {}
|
||||||
maxId: flakeId ? maxId : Number.parseInt(maxId, 10),
|
if (maxId !== undefined)
|
||||||
minId: flakeId ? minId : Number.parseInt(minId, 10),
|
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) => {
|
export const parseChat = (chat) => {
|
||||||
|
|
|
||||||
|
|
@ -400,9 +400,13 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: await Promise.all(
|
items: await Promise.all(
|
||||||
users.map((user) => {
|
users.map(async (user) => {
|
||||||
useUsersStore().updateUserAdminData(user.id, user)
|
const fullUser = await useUsersStore().fetchUserIfMissing({
|
||||||
return useUsersStore().findUser(user.id)
|
id: user.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (fullUser) useUsersStore().updateUserAdminData(user.id, user)
|
||||||
|
return fullUser
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
count,
|
count,
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,8 @@ export const useChatsStore = defineStore('chats', {
|
||||||
updateChat(updatedChat) {
|
updateChat(updatedChat) {
|
||||||
const chat = this.data.get(updatedChat.id)
|
const chat = this.data.get(updatedChat.id)
|
||||||
if (chat) {
|
if (chat) {
|
||||||
const isNewMessage = chat.lastMessage !== updatedChat.lastMessage
|
const isNewMessage =
|
||||||
|
chat.lastMessage?.id !== updatedChat.lastMessage?.id
|
||||||
chat.lastMessage = updatedChat.lastMessage
|
chat.lastMessage = updatedChat.lastMessage
|
||||||
chat.unread = updatedChat.unread
|
chat.unread = updatedChat.unread
|
||||||
chat.updated_at = updatedChat.updated_at
|
chat.updated_at = updatedChat.updated_at
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ const notificationsFetcher = (credentials) => {
|
||||||
const notifications = response.data
|
const notifications = response.data
|
||||||
if (older && notifications.length === 0) bottomedOut.value = true
|
if (older && notifications.length === 0) bottomedOut.value = true
|
||||||
|
|
||||||
useNotificationsStore().addNewNotifications(response)
|
useNotificationsStore().addNewNotifications(response, older)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (
|
||||||
error.statusCode === 400 &&
|
error.statusCode === 400 &&
|
||||||
|
|
@ -78,16 +78,13 @@ const notificationsFetcher = (credentials) => {
|
||||||
|
|
||||||
args.timeline = 'notifications'
|
args.timeline = 'notifications'
|
||||||
if (older) {
|
if (older) {
|
||||||
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
|
if (timelineData.minId !== '') {
|
||||||
args.maxId = timelineData.minId
|
args.maxId = timelineData.minId
|
||||||
}
|
}
|
||||||
return await fetchNotifications({ args, older })
|
return await fetchNotifications({ args, older })
|
||||||
} else {
|
} else {
|
||||||
// fetch new notifications
|
// fetch new notifications
|
||||||
if (
|
if (sinceId === undefined && timelineData.maxId !== '') {
|
||||||
sinceId === undefined &&
|
|
||||||
timelineData.maxId !== Number.POSITIVE_INFINITY
|
|
||||||
) {
|
|
||||||
args.sinceId = timelineData.maxId
|
args.sinceId = timelineData.maxId
|
||||||
} else if (sinceId !== null) {
|
} else if (sinceId !== null) {
|
||||||
args.sinceId = sinceId
|
args.sinceId = sinceId
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,11 @@ const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
|
|
||||||
const numStatusesBeforeFetch = timeline.statusIds.size
|
const numStatusesBeforeFetch = timeline.statusIds.size
|
||||||
|
|
||||||
if (older && bottomedOut.value) return
|
if (older && bottomedOut.value) {
|
||||||
|
loadingOlder.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return fetchTimeline(args)
|
return fetchTimeline(args)
|
||||||
.then(({ data, pagination, timestamp }) => {
|
.then(({ data, pagination, timestamp }) => {
|
||||||
// No statuses for timeline, ever.
|
// No statuses for timeline, ever.
|
||||||
|
|
@ -135,6 +139,9 @@ const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
loadingOlder,
|
loadingOlder,
|
||||||
loadingNewer,
|
loadingNewer,
|
||||||
bottomedOut,
|
bottomedOut,
|
||||||
|
resetBottomedOut: () => {
|
||||||
|
bottomedOut.value = false
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -134,14 +134,7 @@ export const useInterfaceStore = defineStore('interface', {
|
||||||
1001, // Going away
|
1001, // Going away
|
||||||
])
|
])
|
||||||
const { code } = closeEvent.original
|
const { code } = closeEvent.original
|
||||||
if (intendedCodes.has(code)) {
|
if (!intendedCodes.has(code)) {
|
||||||
this.pushGlobalNotice({
|
|
||||||
level: 'success',
|
|
||||||
messageKey: 'timeline.socket_closed',
|
|
||||||
messageArgs: [code],
|
|
||||||
timeout: 5000,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.pushGlobalNotice({
|
this.pushGlobalNotice({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
messageKey: 'timeline.socket_broke',
|
messageKey: 'timeline.socket_broke',
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,9 @@ export const useListsStore = defineStore('lists', {
|
||||||
},
|
},
|
||||||
setLists(value) {
|
setLists(value) {
|
||||||
this.allLists = value
|
this.allLists = value
|
||||||
|
this.allListsObject = Object.fromEntries(
|
||||||
|
value.map((list) => [list.id, list]),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
async createList({ title }) {
|
async createList({ title }) {
|
||||||
return await createList({
|
return await createList({
|
||||||
|
|
|
||||||
|
|
@ -81,13 +81,15 @@ export const useNotificationsStore = defineStore('notifications', {
|
||||||
pause() {
|
pause() {
|
||||||
this.paused = true
|
this.paused = true
|
||||||
if (this.fetcher && this.fetching) {
|
if (this.fetcher && this.fetching) {
|
||||||
this.stopFetching('Notifications paused')
|
console.debug('[Notifications] Pausing notifications')
|
||||||
|
this.fetcher.stopFetching()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resume() {
|
resume() {
|
||||||
this.paused = false
|
this.paused = false
|
||||||
if (this.fetcher && this.fetching) {
|
if (this.fetcher && this.fetching) {
|
||||||
this.startFetching('Notifications resumed')
|
console.debug('[Notifications] Resuming notifications')
|
||||||
|
this.fetcher.startFetching()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
activate() {
|
activate() {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ export const defaultState = () => ({
|
||||||
conversations: new Map(),
|
conversations: new Map(),
|
||||||
favorites: new Set(),
|
favorites: new Set(),
|
||||||
socket: null,
|
socket: null,
|
||||||
|
favs: new Map(),
|
||||||
|
repeats: new Map(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useStatusesStore = defineStore('statuses', {
|
export const useStatusesStore = defineStore('statuses', {
|
||||||
|
|
@ -188,61 +190,69 @@ export const useStatusesStore = defineStore('statuses', {
|
||||||
return fetchEmojiReactions({
|
return fetchEmojiReactions({
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: emojiReactions }) => {
|
}).then(({ data, timestamp }) => {
|
||||||
this.addEmojiReactionsBy({
|
const reactions = data.map((reaction) => {
|
||||||
id,
|
const users = useUsersStore().addNewUsers({
|
||||||
emojiReactions,
|
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) {
|
fetchFavs(id) {
|
||||||
return fetchFavoritedByUsers({
|
return fetchFavoritedByUsers({
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: favoritedByUsers }) =>
|
}).then((result) => {
|
||||||
this.addFavs({
|
const users = useUsersStore().addNewUsers(result)
|
||||||
id,
|
return this.addFavs(id, new Set(users.map(({ id }) => id)))
|
||||||
favoritedByUsers,
|
})
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
fetchRepeats(id) {
|
fetchRepeats(id) {
|
||||||
return fetchRebloggedByUsers({
|
return fetchRebloggedByUsers({
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: rebloggedByUsers }) =>
|
}).then((result) => {
|
||||||
this.addRepeats({
|
const users = useUsersStore().addNewUsers(result)
|
||||||
id,
|
return this.addRepeats(id, new Set(users.map(({ id }) => id)))
|
||||||
rebloggedByUsers,
|
})
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
fetchFavsAndRepeats(id) {
|
fetchFavsAndRepeats(id) {
|
||||||
return Promise.all([this.fetchFavs(id), this.fetchRepeats(id)])
|
return Promise.all([this.fetchFavs(id), this.fetchRepeats(id)])
|
||||||
},
|
},
|
||||||
|
|
||||||
// Updates
|
// Updates
|
||||||
addRepeats({ id, rebloggedByUsers }) {
|
addRepeats(id, users) {
|
||||||
const currentUser = useUsersStore().currentUser
|
const currentUser = useUsersStore().currentUser
|
||||||
const newStatus = this.allStatuses.get(id)
|
const newStatus = this.allStatuses.get(id)
|
||||||
newStatus.rebloggedBy = rebloggedByUsers.filter(Boolean)
|
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 = newStatus.rebloggedBy.length
|
// repeats stats can be incorrect based on polling
|
||||||
newStatus.repeated = !!newStatus.rebloggedBy.find(
|
// condition, let's update them using the most recent data
|
||||||
({ id }) => currentUser?.id === id,
|
newStatus.repeat_num = users.size
|
||||||
)
|
newStatus.repeated = users.has(currentUser?.id)
|
||||||
},
|
},
|
||||||
addFavs({ id, favoritedByUsers }) {
|
addFavs(id, users) {
|
||||||
const currentUser = useUsersStore().currentUser
|
const currentUser = useUsersStore().currentUser
|
||||||
const newStatus = this.allStatuses.get(id)
|
const newStatus = this.allStatuses.get(id)
|
||||||
newStatus.favoritedBy = favoritedByUsers.filter(Boolean)
|
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 = newStatus.favoritedBy.length
|
// favorites stats can be incorrect based on polling
|
||||||
newStatus.favorited = !!newStatus.favoritedBy.find(
|
// condition, let's update them using the most recent data
|
||||||
({ id }) => currentUser?.id === id,
|
newStatus.fave_num = users.size
|
||||||
)
|
newStatus.favorited = users.has(currentUser?.id)
|
||||||
},
|
},
|
||||||
addEmojiReactionsBy({ id, emojiReactions }) {
|
addEmojiReactionsBy(id, emojiReactions) {
|
||||||
const status = this.allStatuses.get(id)
|
const status = this.allStatuses.get(id)
|
||||||
status.emoji_reactions = emojiReactions
|
status.emoji_reactions = emojiReactions
|
||||||
},
|
},
|
||||||
|
|
@ -287,7 +297,7 @@ export const useStatusesStore = defineStore('statuses', {
|
||||||
useInterfaceStore().pushGlobalNotice({
|
useInterfaceStore().pushGlobalNotice({
|
||||||
level: 'error',
|
level: 'error',
|
||||||
messageKey: 'status.interact_error',
|
messageKey: 'status.interact_error',
|
||||||
messageArgs: [error],
|
messageArgs: { error },
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -393,6 +403,7 @@ export const useStatusesStore = defineStore('statuses', {
|
||||||
name: emoji,
|
name: emoji,
|
||||||
count: 0,
|
count: 0,
|
||||||
accounts: [],
|
accounts: [],
|
||||||
|
account_ids: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = value ? reaction.count + 1 : reaction.count - 1
|
const count = value ? reaction.count + 1 : reaction.count - 1
|
||||||
|
|
@ -400,12 +411,14 @@ export const useStatusesStore = defineStore('statuses', {
|
||||||
const accounts = value
|
const accounts = value
|
||||||
? [...reaction.accounts, currentUser]
|
? [...reaction.accounts, currentUser]
|
||||||
: reaction.accounts.filter((acc) => acc.id !== currentUser.id)
|
: reaction.accounts.filter((acc) => acc.id !== currentUser.id)
|
||||||
|
const account_ids = accounts.filter(Boolean).map(({ id }) => id)
|
||||||
|
|
||||||
const newReaction = {
|
const newReaction = {
|
||||||
...reaction,
|
...reaction,
|
||||||
count,
|
count,
|
||||||
me: value,
|
me: value,
|
||||||
accounts,
|
accounts,
|
||||||
|
account_ids,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reactionPresent && count > 0) {
|
if (reactionPresent && count > 0) {
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ export const useStreamingStore = defineStore('streaming', {
|
||||||
this.subscribers.delete(subscriber)
|
this.subscribers.delete(subscriber)
|
||||||
if (stream) {
|
if (stream) {
|
||||||
this.subscriptions.get(stream.name).delete(stream.argument)
|
this.subscriptions.get(stream.name).delete(stream.argument)
|
||||||
|
} else {
|
||||||
|
this.globalSubscriptions.delete(subscriber)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream && this.state === WSConnectionStatus.JOINED) {
|
if (stream && this.state === WSConnectionStatus.JOINED) {
|
||||||
|
|
@ -106,6 +108,8 @@ export const useStreamingStore = defineStore('streaming', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
initSocket(initial) {
|
initSocket(initial) {
|
||||||
|
if (this.socket) throw new Error('Socket already exists!')
|
||||||
|
|
||||||
this.state = initial
|
this.state = initial
|
||||||
? WSConnectionStatus.STARTING_INITIAL
|
? WSConnectionStatus.STARTING_INITIAL
|
||||||
: WSConnectionStatus.STARTING
|
: WSConnectionStatus.STARTING
|
||||||
|
|
@ -127,7 +131,11 @@ export const useStreamingStore = defineStore('streaming', {
|
||||||
},
|
},
|
||||||
stopSocket() {
|
stopSocket() {
|
||||||
this.socket.close()
|
this.socket.close()
|
||||||
|
this.socket = null
|
||||||
this.state = WSConnectionStatus.CLOSED
|
this.state = WSConnectionStatus.CLOSED
|
||||||
|
this.retrying = false
|
||||||
|
this.retryMultiplier = 1
|
||||||
|
this.error = null
|
||||||
},
|
},
|
||||||
|
|
||||||
getSubArgs(stream) {
|
getSubArgs(stream) {
|
||||||
|
|
@ -227,6 +235,8 @@ export const useStreamingStore = defineStore('streaming', {
|
||||||
)
|
)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
if (this.retrying) return // retry aborted (i.e. due to logout)
|
||||||
|
|
||||||
this.initSocket()
|
this.initSocket()
|
||||||
}, retryTimeout(this.retryMultiplier))
|
}, retryTimeout(this.retryMultiplier))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ export const ARGUMENT_MAP = {
|
||||||
user: 'userId',
|
user: 'userId',
|
||||||
userPinned: 'userId',
|
userPinned: 'userId',
|
||||||
media: 'userId',
|
media: 'userId',
|
||||||
|
favorites: 'userId',
|
||||||
}
|
}
|
||||||
|
|
||||||
const TIMELINES = new Set([
|
const TIMELINES = new Set([
|
||||||
|
|
@ -105,8 +106,6 @@ export const defaultState = () => {
|
||||||
return Object.fromEntries([...TIMELINES].map((name) => [name, emptyTl(name)]))
|
return Object.fromEntries([...TIMELINES].map((name) => [name, emptyTl(name)]))
|
||||||
}
|
}
|
||||||
|
|
||||||
//const CUSTOM_SORT = new Set(['bookmarks', 'favorites'])
|
|
||||||
|
|
||||||
export const useTimelinesStore = defineStore('timelines', {
|
export const useTimelinesStore = defineStore('timelines', {
|
||||||
state: defaultState,
|
state: defaultState,
|
||||||
actions: {
|
actions: {
|
||||||
|
|
@ -182,7 +181,7 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
timeline.socket.handlers
|
timeline.socket.handlers
|
||||||
timeline.socket.et.removeEventListener('open', openHandler)
|
timeline.socket.et.removeEventListener('open', openHandler)
|
||||||
timeline.socket.et.removeEventListener('close', closeHandler)
|
timeline.socket.et.removeEventListener('close', closeHandler)
|
||||||
timeline.socket.et.removeEventListener('message', messageHandler)
|
timeline.socket.et.removeEventListener('update', messageHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
this[timelineName] = emptyTl(timelineName)
|
this[timelineName] = emptyTl(timelineName)
|
||||||
|
|
@ -198,6 +197,7 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
timeline.maxId = ''
|
timeline.maxId = ''
|
||||||
timeline.minId = ''
|
timeline.minId = ''
|
||||||
timeline.reloadNeeded = false
|
timeline.reloadNeeded = false
|
||||||
|
timeline.fetcher.resetBottomedOut()
|
||||||
},
|
},
|
||||||
activatePersistents() {
|
activatePersistents() {
|
||||||
TIMELINES.forEach((name) => {
|
TIMELINES.forEach((name) => {
|
||||||
|
|
@ -268,8 +268,6 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
if (statuses.length === 0) return
|
if (statuses.length === 0) return
|
||||||
const timeline = this[timelineName]
|
const timeline = this[timelineName]
|
||||||
|
|
||||||
this.populateRepeats(timeline, repeats)
|
|
||||||
|
|
||||||
// This makes sure that user timeline won't get data meant for other
|
// This makes sure that user timeline won't get data meant for other
|
||||||
// user. I.e. opening different user profiles makes request which could
|
// user. I.e. opening different user profiles makes request which could
|
||||||
// return data late after user already viewing different user profile
|
// return data late after user already viewing different user profile
|
||||||
|
|
@ -280,9 +278,7 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noIdUpdate) {
|
this.populateRepeats(timeline, repeats)
|
||||||
this.updateTimelineExtremes(timeline, pagination)
|
|
||||||
}
|
|
||||||
|
|
||||||
const filtered = statuses.filter((id) => !timeline.statusIds.has(id))
|
const filtered = statuses.filter((id) => !timeline.statusIds.has(id))
|
||||||
if (older) {
|
if (older) {
|
||||||
|
|
@ -291,29 +287,39 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
timeline.order.unshift(...filtered)
|
timeline.order.unshift(...filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newStatuses = new Set()
|
||||||
|
|
||||||
statuses.forEach((statusId) => {
|
statuses.forEach((statusId) => {
|
||||||
const isNew = !timeline.statusIds.has(statusId)
|
const isNew = !timeline.statusIds.has(statusId)
|
||||||
timeline.statusIds.add(statusId)
|
timeline.statusIds.add(statusId)
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
const seenBefore = this.checkSeenBefore(timeline, statusId)
|
newStatuses.add(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.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) {
|
onStreamMessage(timelineName, argument, event) {
|
||||||
this.addStatusesToTimeline(timeline, argument, {
|
this.addStatusesToTimeline(timelineName, argument, {
|
||||||
statuses: event.data.map(({ id }) => id),
|
statuses: event.data.map(({ id }) => id),
|
||||||
repeats: event.data
|
repeats: event.data
|
||||||
.filter(({ retweeted_status }) => Boolean(retweeted_status))
|
.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 it's the only reprööt then we've never seen post before
|
||||||
if (knownRepeats.size === 1) return false
|
if (knownRepeats.size === 1) return false
|
||||||
// If we're working on oldest known reprööt then we've never seen it before
|
// 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
|
// Poll & Push
|
||||||
|
|
@ -416,7 +422,7 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Queues & Timeline manip
|
// 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)
|
// Can't use Math.min/max because it doesn't work with string (duh)
|
||||||
const minNew = pagination.maxId ?? last(timeline.order) ?? ''
|
const minNew = pagination.maxId ?? last(timeline.order) ?? ''
|
||||||
const maxNew = pagination.minId ?? first(timeline.order) ?? ''
|
const maxNew = pagination.minId ?? first(timeline.order) ?? ''
|
||||||
|
|
@ -424,10 +430,10 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
const newer = maxNew > timeline.maxId
|
const newer = maxNew > timeline.maxId
|
||||||
const older = minNew < timeline.minId
|
const older = minNew < timeline.minId
|
||||||
|
|
||||||
if (newer || timeline.maxId === '') {
|
if (force || newer || timeline.maxId === '') {
|
||||||
timeline.maxId = maxNew
|
timeline.maxId = maxNew
|
||||||
}
|
}
|
||||||
if (older || timeline.minId === '') {
|
if (force || older || timeline.minId === '') {
|
||||||
timeline.minId = minNew
|
timeline.minId = minNew
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -444,7 +450,8 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
timeline.visibleStatusIds = new Set([
|
timeline.visibleStatusIds = new Set([
|
||||||
...timeline.order.filter((id) => !timeline.ignoredIds.has(id)),
|
...timeline.order.filter((id) => !timeline.ignoredIds.has(id)),
|
||||||
])
|
])
|
||||||
this.updateTimelineExtremes(timeline)
|
this.updateTimelineExtremes(timeline, {}, true)
|
||||||
|
timeline.fetcher.resetBottomedOut()
|
||||||
},
|
},
|
||||||
syncOrder(timeline) {
|
syncOrder(timeline) {
|
||||||
timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id))
|
timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id))
|
||||||
|
|
@ -453,8 +460,10 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
this[timeline].reloadNeeded = true
|
this[timeline].reloadNeeded = true
|
||||||
},
|
},
|
||||||
requireReloadAll() {
|
requireReloadAll() {
|
||||||
Object.keys(this).forEach((timeline) => {
|
TIMELINES.forEach((timelineName) => {
|
||||||
this[timeline].reloadNeeded = true
|
const timeline = this[timelineName]
|
||||||
|
|
||||||
|
timeline.reloadNeeded = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,12 @@ export const useUsersStore = defineStore('users', {
|
||||||
// Misc updates
|
// Misc updates
|
||||||
updateUserAdminData(id, data) {
|
updateUserAdminData(id, data) {
|
||||||
const user = this.users.get(id)
|
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.adminData = data
|
||||||
user.deactivated = !data.is_active
|
user.deactivated = !data.is_active
|
||||||
user.tags = new Set(data.tags)
|
user.tags = new Set(data.tags)
|
||||||
|
|
@ -271,15 +276,21 @@ export const useUsersStore = defineStore('users', {
|
||||||
|
|
||||||
const result = await promise
|
const result = await promise
|
||||||
|
|
||||||
if (result) {
|
try {
|
||||||
const { id, screen_name } = result
|
if (result) {
|
||||||
|
const { id, screen_name } = result
|
||||||
|
|
||||||
// Save promise for future use
|
// Save promise for future use
|
||||||
this.fetchesIds.set(id, promise)
|
this.fetchesIds.set(id, promise)
|
||||||
this.fetchesNames.set(screen_name, promise)
|
this.fetchesNames.set(screen_name, promise)
|
||||||
return this.users.get(id)
|
return this.users.get(id)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Failed fetching user ${identifier}`, e)
|
||||||
|
map.delete(identifier)
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async fetchUser(id) {
|
async fetchUser(id) {
|
||||||
|
|
@ -513,7 +524,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
|
|
||||||
/// Mute
|
/// Mute
|
||||||
muteUser(id, expiresIn = 0) {
|
muteUser(id, expiresIn = 0) {
|
||||||
const predictedRelationship = this.relationships[id] || { id }
|
const predictedRelationship = this.relationships.get(id) || { id }
|
||||||
predictedRelationship.muting = true
|
predictedRelationship.muting = true
|
||||||
this.updateUserRelationships({
|
this.updateUserRelationships({
|
||||||
optimism: true,
|
optimism: true,
|
||||||
|
|
@ -532,7 +543,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
return Promise.all(data.map((d) => this.muteUser(d)))
|
return Promise.all(data.map((d) => this.muteUser(d)))
|
||||||
},
|
},
|
||||||
unmuteUser(id) {
|
unmuteUser(id) {
|
||||||
const predictedRelationship = this.relationships[id] || { id }
|
const predictedRelationship = this.relationships.get(id) || { id }
|
||||||
predictedRelationship.muting = false
|
predictedRelationship.muting = false
|
||||||
this.updateUserRelationships({
|
this.updateUserRelationships({
|
||||||
optimism: true,
|
optimism: true,
|
||||||
|
|
@ -549,7 +560,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
|
|
||||||
/// Block
|
/// Block
|
||||||
blockUser(id, expiresIn = 0) {
|
blockUser(id, expiresIn = 0) {
|
||||||
const predictedRelationship = this.relationships[id] || { id }
|
const predictedRelationship = this.relationships.get(id) || { id }
|
||||||
this.updateUserRelationships({
|
this.updateUserRelationships({
|
||||||
optimism: true,
|
optimism: true,
|
||||||
data: [predictedRelationship],
|
data: [predictedRelationship],
|
||||||
|
|
@ -718,6 +729,8 @@ export const useUsersStore = defineStore('users', {
|
||||||
useAnnouncementsStore().stopFetching()
|
useAnnouncementsStore().stopFetching()
|
||||||
useListsStore().stopFetching()
|
useListsStore().stopFetching()
|
||||||
useBookmarkFoldersStore().stopFetching()
|
useBookmarkFoldersStore().stopFetching()
|
||||||
|
useChatsStore().stopFetching()
|
||||||
|
|
||||||
store?.dispatch('stopFetchingFollowRequests')
|
store?.dispatch('stopFetchingFollowRequests')
|
||||||
|
|
||||||
// NOTE: No need to verify the app still exists, because if it doesn't,
|
// 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
|
// Full reset on logout success
|
||||||
useTimelinesStore().deactivateAll()
|
useTimelinesStore().deactivateAll()
|
||||||
useStatusesStore().resetStatuses()
|
useStatusesStore().resetStatuses()
|
||||||
useChatsStore().stopFetching()
|
|
||||||
useChatsStore().resetChats()
|
useChatsStore().resetChats()
|
||||||
|
|
||||||
this.users = new Map()
|
this.users = new Map()
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,10 @@ describe('Statuses store', () => {
|
||||||
'EmojiReactions',
|
'EmojiReactions',
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
accounts: [mockMastoAPIUser()],
|
accounts: [
|
||||||
|
mockMastoAPIUser({ id: 'u1' }),
|
||||||
|
mockMastoAPIUser({ id: 'u2' }),
|
||||||
|
],
|
||||||
count: 1,
|
count: 1,
|
||||||
me: false,
|
me: false,
|
||||||
name: 'cofe',
|
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) => {
|
])('fetch%s', async (group, mockedResponse) => {
|
||||||
const mockFetch = vi.fn()
|
const mockFetch = vi.fn()
|
||||||
mockFetch.mockResolvedValueOnce(
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
|
@ -309,6 +318,7 @@ describe('Statuses store', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
vi.stubGlobal('fetch', mockFetch)
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
const addNewUsers = vi.spyOn(useUsersStore(), 'addNewUsers')
|
||||||
|
|
||||||
let urlKey
|
let urlKey
|
||||||
let prefix = 'MASTODON'
|
let prefix = 'MASTODON'
|
||||||
|
|
@ -335,13 +345,29 @@ describe('Statuses store', () => {
|
||||||
const result = await store[`fetch${group}`]('id')
|
const result = await store[`fetch${group}`]('id')
|
||||||
const updated = store.allStatuses.get('id')
|
const updated = store.allStatuses.get('id')
|
||||||
|
|
||||||
|
// Fetch called
|
||||||
expect(mockFetch).to.have.been.calledWith(url, DEFAULT_OPTIONS())
|
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') {
|
if (group === 'Favs') {
|
||||||
expect(updated.favoritedBy).to.have.length(1)
|
expect(store.favs).to.have.length(1)
|
||||||
expect(updated.fave_num).to.eql(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') {
|
} else if (group === 'Repeats') {
|
||||||
expect(updated.rebloggedBy).to.have.length(1)
|
expect(store.repeats).to.have.length(1)
|
||||||
expect(updated.repeat_num).to.eql(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') {
|
} else if (group === 'EmojiReactions') {
|
||||||
expect(updated.emoji_reactions).to.have.length(mockedResponse.length)
|
expect(updated.emoji_reactions).to.have.length(mockedResponse.length)
|
||||||
expect(updated.emoji_reactions[0].name).to.eql(mockedResponse[0].name)
|
expect(updated.emoji_reactions[0].name).to.eql(mockedResponse[0].name)
|
||||||
|
|
|
||||||
|
|
@ -740,19 +740,24 @@ describe('Users store', () => {
|
||||||
|
|
||||||
const spies = [
|
const spies = [
|
||||||
// Misc initialization
|
// Misc initialization
|
||||||
vi.spyOn(useStatusesStore(), 'resetStatuses'),
|
/* 0 */ vi.spyOn(useStatusesStore(), 'resetStatuses'),
|
||||||
vi.spyOn(useInterfaceStore(), 'onLogout'),
|
/* 1 */ vi.spyOn(useInterfaceStore(), 'onLogout'),
|
||||||
|
|
||||||
// Timeline / Notifications
|
// Timeline / Notifications
|
||||||
vi.spyOn(useNotificationsStore(), 'deactivate'),
|
/* 2 */ vi.spyOn(useNotificationsStore(), 'deactivate'),
|
||||||
vi.spyOn(useTimelinesStore(), 'deactivateAll'),
|
/* 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
|
// Fetchers
|
||||||
vi.spyOn(useChatsStore(), 'resetChats'),
|
/* 8 */ vi.spyOn(useChatsStore(), 'resetChats'),
|
||||||
vi.spyOn(useListsStore(), 'stopFetching'),
|
/* 9 */ vi.spyOn(useChatsStore(), 'stopFetching'),
|
||||||
vi.spyOn(useAnnouncementsStore(), 'stopFetching'),
|
/* 10 */ vi.spyOn(useListsStore(), 'stopFetching'),
|
||||||
vi.spyOn(useBookmarkFoldersStore(), 'stopFetching'),
|
/* 11 */ vi.spyOn(useAnnouncementsStore(), 'stopFetching'),
|
||||||
vi.spyOn(useStreamingStore(), 'stopSocket'),
|
/* 12 */ vi.spyOn(useBookmarkFoldersStore(), 'stopFetching'),
|
||||||
|
/* 13 */ vi.spyOn(useStreamingStore(), 'stopSocket'),
|
||||||
]
|
]
|
||||||
|
|
||||||
spies.forEach((spy) => {
|
spies.forEach((spy) => {
|
||||||
|
|
@ -791,6 +796,91 @@ describe('Users store', () => {
|
||||||
expect(spy, `Spy ${index} has failed`).to.have.been.called
|
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