i think it's usable now

This commit is contained in:
Henry Jameson 2026-08-10 22:26:22 +03:00
commit 37f501108b
42 changed files with 296 additions and 1295 deletions

View file

@ -1,15 +1,11 @@
import AuthForm from 'src/components/auth_form/auth_form.js'
import BookmarkTimeline from 'src/components/bookmark_timeline/bookmark_timeline.vue'
import BubbleTimeline from 'src/components/bubble_timeline/bubble_timeline.vue'
import ConversationPage from 'src/components/conversation-page/conversation-page.vue'
import DMs from 'src/components/dm_timeline/dm_timeline.vue'
import FriendsTimeline from 'src/components/friends_timeline/friends_timeline.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import PublicAndExternalTimeline from 'src/components/public_and_external_timeline/public_and_external_timeline.vue'
import PublicTimeline from 'src/components/public_timeline/public_timeline.vue'
import QuotesTimeline from 'src/components/quotes_timeline/quotes_timeline.vue'
import RemoteUserResolver from 'src/components/remote_user_resolver/remote_user_resolver.vue'
import TagTimeline from 'src/components/tag_timeline/tag_timeline.vue'
import Timeline from 'src/components/timeline/timeline.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
@ -42,22 +38,38 @@ export default (store) => {
{
name: 'public-external-timeline',
path: '/main/all',
component: PublicAndExternalTimeline,
component: Timeline,
props: () => ({
timelineName: 'publicAndExternal',
}),
},
{
name: 'public-timeline',
path: '/main/public',
component: PublicTimeline,
component: Timeline,
props: () => ({
timelineName: 'public',
}),
},
{
name: 'friends',
path: '/main/friends',
component: FriendsTimeline,
component: Timeline,
beforeEnter: validateAuthenticatedRoute,
props: () => ({
timelineName: 'friends',
}),
},
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{
name: 'bubble',
path: '/bubble',
component: Timeline,
props: () => ({
timelineName: 'bubble',
}),
},
{
name: 'conversation',
path: '/notice/:id',
@ -105,8 +117,11 @@ export default (store) => {
{
name: 'dms',
path: '/users/:username/dms',
component: DMs,
component: Timeline,
beforeEnter: validateAuthenticatedRoute,
props: () => ({
timelineName: 'dms',
}),
},
{
name: 'registration',

View file

@ -1,8 +1,10 @@
import Timeline from 'src/components/timeline/timeline.vue'
import { useStatusesStore } from 'src/stores/statuses.js'
const Bookmarks = {
created() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
useStatusesStore().clearTimeline({ timeline: 'bookmarks' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
bookmarkFolderId: this.folderId || null,
@ -21,7 +23,7 @@ const Bookmarks = {
},
watch: {
folderId() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
useStatusesStore().clearTimeline({ timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
this.$store.dispatch('startFetchingTimeline', {
timeline: 'bookmarks',
@ -30,7 +32,7 @@ const Bookmarks = {
},
},
unmounted() {
this.$store.commit('clearTimeline', { timeline: 'bookmarks' })
useStatusesStore().clearTimeline({ timeline: 'bookmarks' })
this.$store.dispatch('stopFetchingTimeline', 'bookmarks')
},
}

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.bubble')"
:timeline="timeline"
:timeline-name="'bubble'"
/>
</template>
<script src="./bubble_timeline.js"></script>

View file

@ -19,6 +19,7 @@ import UserPopover from 'src/components/user_popover/user_popover.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInterfaceStore } from 'src/stores/interface'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -100,9 +101,9 @@ const ChatMessage = {
return !this.message.in_reply_to_status_id
},
customReplyTo() {
return this.$store.state.statuses.allStatusesObject[
this.message.in_reply_to_status_id
]
return useStatusesStore().allStatuses.get(
this.message.in_reply_to_status_id,
)
},
replyToName() {
if (this.message.in_reply_to_screen_name) {

View file

@ -19,6 +19,7 @@ import { useChatsStore } from 'src/stores/chats.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import {
@ -122,7 +123,7 @@ const Chat = {
},
computed: {
conversationId() {
const status = this.$store.state.statuses.allStatusesObject[this.statusId]
const status = useStatusesStore().allStatuses.get(this.statusId)
return get(
status,
'retweeted_status.statusnet_conversation_id',

View file

@ -1,4 +1,4 @@
import { clone, filter, findIndex, get, reduce } from 'lodash'
import { get, reduce } from 'lodash'
import { mapState as mapPiniaState } from 'pinia'
import { mapState } from 'vuex'
@ -9,9 +9,10 @@ import QuickViewSettings from 'src/components/quick_view_settings/quick_view_set
import RichContent from 'src/components/rich_content/rich_content.jsx'
import ThreadTree from 'src/components/thread_tree/thread_tree.vue'
import { useInterfaceStore } from 'src/stores/interface'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { fetchConversation, fetchStatus } from 'src/api/public.js'
import { WSConnectionStatus } from 'src/api/websocket.js'
@ -51,20 +52,6 @@ const sortById = (a, b) => {
}
}
const sortAndFilterConversation = (conversation, statusoid) => {
if (statusoid.type === 'retweet') {
conversation = filter(
conversation,
(status) =>
status.type === 'retweet' ||
status.id !== statusoid.retweeted_status.id,
)
} else {
conversation = filter(conversation, (status) => status.type !== 'retweet')
}
return conversation.filter(Boolean).sort(sortById)
}
const conversation = {
props: {
statusId: {
@ -166,7 +153,7 @@ const conversation = {
return this.virtualHidden && this.suspendable
},
status() {
return this.$store.state.statuses.allStatusesObject[this.statusId]
return useStatusesStore().allStatuses.get(this.statusId)
},
originalStatusId() {
if (this.status.retweeted_status) {
@ -187,15 +174,11 @@ const conversation = {
return [this.status]
}
const conversation = clone(
this.$store.state.statuses.conversationsObject[this.conversationId],
const conversation = useStatusesStore().conversations.get(
this.conversationId,
)
const statusIndex = findIndex(conversation, { id: this.originalStatusId })
if (statusIndex !== -1) {
conversation[statusIndex] = this.status
}
return sortAndFilterConversation(conversation, this.status)
return [...conversation.values()].toSorted(sortById)
},
statusMap() {
return this.conversation.reduce((res, s) => {
@ -441,7 +424,7 @@ const conversation = {
}
},
virtualHidden() {
this.$store.dispatch('setVirtualHeight', {
useStatusesStore().setVirtualHeight({
statusId: this.statusId,
height: `${this.$el.clientHeight}px`,
})
@ -453,9 +436,12 @@ const conversation = {
fetchConversation({
id: this.statusId,
credentials: useOAuthStore().token,
}).then(({ data: { ancestors, descendants } }) => {
this.$store.dispatch('addNewStatuses', { statuses: ancestors })
this.$store.dispatch('addNewStatuses', { statuses: descendants })
}).then(({ data: { ancestors, descendants }, timestamp }) => {
useStatusesStore().addNewStatuses({ statuses: ancestors, timestamp })
useStatusesStore().addNewStatuses({
statuses: descendants,
timestamp,
})
this.setFocused(this.originalStatusId)
})
} else {
@ -482,17 +468,17 @@ const conversation = {
this.focused = id
if (!this.streamingEnabled) {
this.$store.dispatch('fetchStatus', id)
useStatusesStore().fetchStatus(id)
}
this.$store.dispatch('fetchFavsAndRepeats', id)
this.$store.dispatch('fetchEmojiReactionsBy', id)
useStatusesStore().fetchFavsAndRepeats(id)
useStatusesStore().fetchEmojiReactionsBy(id)
},
toggleExpanded() {
this.expanded = !this.expanded
},
getConversationId(statusId) {
const status = this.$store.state.statuses.allStatusesObject[statusId]
const status = useStatusesStore().allStatuses.get(statusId)
return get(
status,
'retweeted_status.statusnet_conversation_id',

View file

@ -1,14 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const DMs = {
computed: {
timeline() {
return this.$store.state.statuses.timelines.dms
},
},
components: {
Timeline,
},
}
export default DMs

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.dms')"
:timeline="timeline"
:timeline-name="'dms'"
/>
</template>
<script src="./dm_timeline.js"></script>

View file

@ -6,6 +6,7 @@ import PostStatusForm from 'src/components/post_status_form/post_status_form.vue
import StatusContent from 'src/components/status_content/status_content.vue'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faPollH } from '@fortawesome/free-solid-svg-icons'
@ -65,7 +66,7 @@ const Draft = {
},
refStatus() {
return this.draft.refId
? this.$store.state.statuses.allStatusesObject[this.draft.refId]
? useStatusesStore().allStatuses.get(this.draft.refId)
: undefined
},
localCollapseSubjectDefault() {

View file

@ -3,6 +3,7 @@ import UserListPopover from 'src/components/user_list_popover/user_list_popover.
import { useInstanceStore } from 'src/stores/instance.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -62,10 +63,7 @@ const EmojiReactions = {
async fetchEmojiReactionsByIfMissing() {
const hasNoAccounts = this.status.emoji_reactions.find((r) => !r.accounts)
if (hasNoAccounts) {
return await this.$store.dispatch(
'fetchEmojiReactionsBy',
this.status.id,
)
return await useStatusesStore().fetchEmojiReactionsBy(this.status.id)
}
},
reactWith(emoji) {

View file

@ -1,14 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const FriendsTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.friends
},
},
}
export default FriendsTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.timeline')"
:timeline="timeline"
:timeline-name="'friends'"
/>
</template>
<script src="./friends_timeline.js"></script>

View file

@ -1,6 +1,7 @@
import Timeline from 'src/components/timeline/timeline.vue'
import { useListsStore } from 'src/stores/lists.js'
import { useStatusesStore } from 'src/stores/statuses.js'
const ListsTimeline = {
data() {
@ -21,7 +22,7 @@ const ListsTimeline = {
if (route.name === 'lists-timeline' && route.params.id !== this.listId) {
this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
useStatusesStore().clearTimeline({ timeline: 'list' })
useListsStore().fetchList({ listId: this.listId })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'list',
@ -40,7 +41,7 @@ const ListsTimeline = {
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
useStatusesStore().clearTimeline({ timeline: 'list' })
},
}

View file

@ -1,22 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const PublicAndExternalTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.publicAndExternal
},
},
created() {
this.$store.dispatch('startFetchingTimeline', {
timeline: 'publicAndExternal',
})
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'publicAndExternal')
},
}
export default PublicAndExternalTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.twkn')"
:timeline="timeline"
:timeline-name="'publicAndExternal'"
/>
</template>
<script src="./public_and_external_timeline.js"></script>

View file

@ -1,20 +0,0 @@
import Timeline from 'src/components/timeline/timeline.vue'
const PublicTimeline = {
components: {
Timeline,
},
computed: {
timeline() {
return this.$store.state.statuses.timelines.public
},
},
created() {
this.$store.dispatch('startFetchingTimeline', { timeline: 'public' })
},
unmounted() {
this.$store.dispatch('stopFetchingTimeline', 'public')
},
}
export default PublicTimeline

View file

@ -1,9 +0,0 @@
<template>
<Timeline
:title="$t('nav.public_tl')"
:timeline="timeline"
:timeline-name="'public'"
/>
</template>
<script src="./public_timeline.js"></script>

View file

@ -5,6 +5,7 @@ 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 { useUsersStore } from 'src/stores/users.js'
@ -27,7 +28,7 @@ const QuickFilterSettings = {
path: 'replyVisibility',
value: visibility,
})
this.$store.dispatch('queueFlushAll')
useStatusesStore().queueFlushAll()
},
openTab(tab) {
useInterfaceStore().openSettingsModalTab(tab)

View file

@ -1,3 +1,5 @@
import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@ -45,7 +47,7 @@ export default {
computed: {
quotedStatus() {
return this.statusId
? this.$store.state.statuses.allStatusesObject[this.statusId]
? useStatusesStore().allStatuses.get(this.statusId)
: undefined
},
shouldDisplayQuote() {
@ -79,8 +81,8 @@ export default {
this.fetchAttempted = true
this.fetching = true
this.$emit('loading', true)
this.$store
.dispatch('fetchStatus', this.statusId)
useStatusesStore()
.fetchStatus(this.statusId)
.then(() => {
this.displayQuote = true
})

View file

@ -1,8 +1,10 @@
import Timeline from 'src/components/timeline/timeline.vue'
import { useStatusesStore } from 'src/stores/statuses.js'
const QuotesTimeline = {
created() {
this.$store.commit('clearTimeline', { timeline: 'quotes' })
useStatusesStore().clearTimeline({ timeline: 'tag' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'quotes',
statusId: this.statusId,
@ -21,7 +23,7 @@ const QuotesTimeline = {
},
watch: {
statusId() {
this.$store.commit('clearTimeline', { timeline: 'quotes' })
useStatusesStore().clearTimeline({ timeline: 'tag' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'quotes',
statusId: this.statusId,

View file

@ -4,6 +4,7 @@ import Conversation from 'src/components/conversation/conversation.vue'
import FollowCard from 'src/components/follow_card/follow_card.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -39,11 +40,11 @@ const Search = {
return this.userIds.map((userId) => useUsersStore().findUser(userId))
},
visibleStatuses() {
const allStatusesObject = this.$store.state.statuses.allStatusesObject
const allStatuses = useStatusesStore().allStatuses
return this.statuses.filter(
(status) =>
allStatusesObject[status.id] && !allStatusesObject[status.id].deleted,
allStatuses.has(status.id) && !allStatuses.get(status.id).deleted,
)
},
},

View file

@ -11,6 +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'
const ClutterTab = {
components: {
@ -35,7 +36,7 @@ const ClutterTab = {
// Updating nested properties
watch: {
replyVisibility() {
this.$store.dispatch('queueFlushAll')
useStatusesStore().queueFlushAll()
},
},
}

View file

@ -14,6 +14,7 @@ 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 {
@ -265,7 +266,7 @@ const FilteringTab = {
// Updating nested properties
watch: {
replyVisibility() {
this.$store.dispatch('queueFlushAll')
useStatusesStore().queueFlushAll()
},
muteFiltersObject() {
this.muteFiltersDraftObject = cloneDeep(

View file

@ -23,6 +23,7 @@ import {
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.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 { useUserHighlightStore } from 'src/stores/user_highlight.js'
import { useUsersStore } from 'src/stores/users.js'
@ -206,7 +207,7 @@ const Status = {
},
statusFromGlobalRepository() {
// NOTE: Consider to replace status with statusFromGlobalRepository
return this.$store.state.statuses.allStatusesObject[this.status.id]
return useStatusesStore().allStatuses.get(this.status.id)
},
loggedIn() {
return !!this.currentUser

View file

@ -3,6 +3,7 @@ import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useReportsStore } from 'src/stores/reports.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useStatusHistoryStore } from 'src/stores/statusHistory.js'
const PRIVATE_SCOPES = new Set(['private', 'direct'])
@ -52,7 +53,7 @@ export const BUTTONS = [
(currentUser.id === status.user.id ||
!PRIVATE_SCOPES.has(status.visibility)),
toggleable: true,
confirm: ({ status, getters }) =>
confirm: ({ status }) =>
!status.repeated && useMergedConfigStore().mergedConfig.modalOnRepeat,
confirmStrings: {
title: 'status.repeat_confirm_title',
@ -60,11 +61,11 @@ export const BUTTONS = [
confirm: 'status.repeat_confirm_accept_button',
cancel: 'status.repeat_confirm_cancel_button',
},
action({ status, dispatch }) {
action({ status }) {
if (!status.repeated) {
return dispatch('retweet', { id: status.id })
return useStatusesStore().retweet(status.id)
} else {
return dispatch('unretweet', { id: status.id })
return useStatusesStore().unretweet(status.id)
}
},
},
@ -82,11 +83,11 @@ export const BUTTONS = [
counter: ({ status }) => status.fave_num,
anonLink: true,
toggleable: true,
action({ status, dispatch }) {
action({ status }) {
if (!status.favorited) {
return dispatch('favorite', { id: status.id })
return useStatusesStore().favorite(status.id)
} else {
return dispatch('unfavorite', { id: status.id })
return useStatusesStore().unfavorite(status.id)
}
},
},
@ -112,7 +113,7 @@ export const BUTTONS = [
if: ({ loggedIn }) => loggedIn,
toggleable: false,
dropdown: true,
action({ status, dispatch, emit }) {
action({ status, emit }) {
/* prevent hiding */
},
},
@ -130,11 +131,11 @@ export const BUTTONS = [
PUBLIC_SCOPES.has(status.visibility)
)
},
action({ status, dispatch }) {
action({ status }) {
if (status.pinned) {
return dispatch('unpinStatus', status.id)
return useStatusesStore().unpinStatus(status.id)
} else {
return dispatch('pinStatus', status.id)
return useStatusesStore().pinStatus(status.id)
}
},
},
@ -150,11 +151,11 @@ export const BUTTONS = [
label: ({ status }) =>
status.bookmarked ? 'status.unbookmark' : 'status.bookmark',
if: ({ loggedIn }) => loggedIn,
action({ status, dispatch }) {
action({ status }) {
if (status.bookmarked) {
return dispatch('unbookmark', { id: status.id })
return useStatusesStore().unbookmark(status.id)
} else {
return dispatch('bookmark', { id: status.id })
return useStatusesStore().bookmark(status.id)
}
},
},
@ -165,7 +166,7 @@ export const BUTTONS = [
name: 'editHistory',
icon: 'history',
label: 'status.status_history',
if({ status, state }) {
if({ status }) {
return (
useInstanceCapabilitiesStore().editingAvailable &&
status.edited_at !== null
@ -196,26 +197,28 @@ export const BUTTONS = [
name: 'edit',
icon: 'pen',
label: 'status.edit',
if({ status, loggedIn, currentUser, state }) {
if({ status, loggedIn, currentUser }) {
return (
loggedIn &&
useInstanceCapabilitiesStore().editingAvailable &&
status.user.id === currentUser.id
)
},
action({ dispatch, status }) {
return dispatch('fetchStatusSource', { id: status.id }).then((data) =>
useEditStatusStore().openEditStatusModal({
statusId: status.id,
statusSubject: data.spoiler_text,
statusText: data.text,
statusIsSensitive: status.nsfw,
statusPoll: status.poll,
statusFiles: [...status.attachments],
statusVisibility: status.visibility,
statusContentType: data.content_type,
}),
)
action({ status }) {
return useStatusesStore()
.fetchStatusSource(status.id)
.then((data) =>
useEditStatusStore().openEditStatusModal({
statusId: status.id,
statusSubject: data.spoiler_text,
statusText: data.text,
statusIsSensitive: status.nsfw,
statusPoll: status.poll,
statusFiles: [...status.attachments],
statusVisibility: status.visibility,
statusContentType: data.content_type,
}),
)
},
},
{
@ -260,15 +263,15 @@ export const BUTTONS = [
currentUser.privileges.has('messages_delete'))
)
},
confirm: ({ getters }) => useMergedConfigStore().mergedConfig.modalOnDelete,
confirm: () => useMergedConfigStore().mergedConfig.modalOnDelete,
confirmStrings: {
title: 'status.delete_confirm_title',
body: 'status.delete_confirm',
confirm: 'status.delete_confirm_accept_button',
cancel: 'status.delete_confirm_cancel_button',
},
action({ dispatch, status }) {
return dispatch('deleteStatus', { id: status.id })
action({ status }) {
return useStatusesStore().deleteStatus(status.id)
},
},
{
@ -287,7 +290,7 @@ export const BUTTONS = [
},
toggleable: false,
dropdown: true,
action({ status, dispatch, emit }) {
action({ status, emit }) {
/* prevent hiding */
},
},
@ -298,7 +301,7 @@ export const BUTTONS = [
name: 'share',
icon: 'share-alt',
label: 'status.copy_link',
action({ state, status, router }) {
action({ status, router }) {
navigator.clipboard.writeText(
[
useInstanceStore().server,

View file

@ -2,6 +2,7 @@ import { get } from 'lodash'
import Modal from 'src/components/modal/modal.vue'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useStatusHistoryStore } from 'src/stores/statusHistory.js'
const StatusHistoryModal = {
@ -50,9 +51,11 @@ const StatusHistoryModal = {
this.statuses = []
},
fetchStatusHistory() {
this.$store.dispatch('fetchStatusHistory', this.params).then((data) => {
this.statuses = data
})
useStatusesStore()
.fetchStatusHistory(this.params)
.then((data) => {
this.statuses = data
})
},
closeModal() {
useStatusHistoryStore().closeStatusHistoryModal()

View file

@ -1,7 +1,7 @@
import { find } from 'lodash'
import Popover from 'src/components/popover/popover.vue'
import { useStatusesStore } from 'src/stores/statuses.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@ -17,7 +17,7 @@ const StatusPopover = {
},
computed: {
status() {
return find(this.$store.state.statuses.allStatuses, { id: this.statusId })
return useStatusesStore().allStatuses.get(this.statusId)
},
},
components: {
@ -30,8 +30,8 @@ const StatusPopover = {
this.error = true
return
}
this.$store
.dispatch('fetchStatus', this.statusId)
useStatusesStore()
.fetchStatus(this.statusId)
.then(() => (this.error = false))
.catch(() => (this.error = true))
}

View file

@ -1,8 +1,10 @@
import Timeline from 'src/components/timeline/timeline.vue'
import { useStatusesStore } from 'src/stores/statuses.js'
const TagTimeline = {
created() {
this.$store.commit('clearTimeline', { timeline: 'tag' })
useStatusesStore().clearTimeline({ timeline: 'tag' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'tag',
tag: this.tag,
@ -21,7 +23,7 @@ const TagTimeline = {
},
watch: {
tag() {
this.$store.commit('clearTimeline', { timeline: 'tag' })
useStatusesStore().clearTimeline({ timeline: 'tag' })
this.$store.dispatch('startFetchingTimeline', {
timeline: 'tag',
tag: this.tag,

View file

@ -9,6 +9,7 @@ import TimelineMenu from 'src/components/timeline_menu/timeline_menu.vue'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import timelineFetcher from 'src/services/timeline_fetcher/timeline_fetcher.service.js'
@ -26,21 +27,19 @@ import {
library.add(faCircleNotch, faCog, faMinus, faArrowUp, faCirclePlus, faCheck)
const Timeline = {
props: [
'timeline',
'timelineName',
'title',
'userId',
'listId',
'statusId',
'bookmarkFolderId',
'tag',
'embedded',
'count',
'pinnedStatusIds',
'inProfile',
'footerSlipgate', // reference to an element where we should put our footer
],
props: {
timelineName: String,
userId: String,
listId: String,
statusId: String,
bookmarkFolderId: String,
tag: String,
embedded: Boolean,
count: Number,
pinnedStatusIds: Set,
inProfile: Boolean,
footerSlipgate: Object, // reference to an element where we should put our footer
},
data() {
return {
showScrollTop: false,
@ -59,8 +58,11 @@ const Timeline = {
QuickViewSettings,
},
computed: {
timeline() {
return useStatusesStore().timelines[this.timelineName]
},
filteredVisibleStatuses() {
return this.timeline.visibleStatuses.filter(
return [...this.timeline.visibleStatuses.values()].filter(
(status) =>
this.timelineName !== 'user' ||
(status.id >= this.timeline.minId &&
@ -116,13 +118,13 @@ const Timeline = {
return keyBy(this.pinnedStatusIds)
},
statusesToDisplay() {
const amount = this.timeline.visibleStatuses.length
const amount = this.timeline.visibleStatuses.size
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
const nonPinnedIndex =
this.virtualScrollIndex - this.filteredPinnedStatusIds.length
const min = Math.max(0, nonPinnedIndex - statusesPerSide)
const max = Math.min(amount, nonPinnedIndex + statusesPerSide)
return this.timeline.visibleStatuses.slice(min, max).map((_) => _.id)
return new Set([...this.timeline.visibleStatuses.keys()].slice(min, max))
},
virtualScrollingEnabled() {
return useMergedConfigStore().mergedConfig.virtualScrolling
@ -143,7 +145,6 @@ const Timeline = {
}
timelineFetcher.fetchAndUpdate({
store,
credentials,
timeline: this.timelineName,
showImmediately,
@ -175,7 +176,7 @@ const Timeline = {
this.handleVisibilityChange,
false,
)
this.$store.commit('setLoading', {
useStatusesStore().setLoading({
timeline: this.timelineName,
value: false,
})
@ -197,30 +198,31 @@ const Timeline = {
},
showNewStatuses() {
if (this.timeline.flushMarker !== 0) {
this.$store.commit('clearTimeline', {
useStatusesStore().clearTimeline({
timeline: this.timelineName,
excludeUserId: true,
})
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
useStatusesStore().queueFlush({ timeline: this.timelineName, id: 0 })
if (this.timelineName === 'user') {
this.$store.dispatch('fetchPinnedStatuses', this.userId)
}
this.fetchOlderStatuses()
} else {
this.blockClicksTemporarily()
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
useStatusesStore().showNewStatuses(this.timelineName)
this.paused = false
}
window.scrollTo({ top: 0 })
},
fetchOlderStatuses: throttle(
function () {
const store = this.$store
const credentials = useUsersStore().currentUser.credentials
store.commit('setLoading', { timeline: this.timelineName, value: true })
useStatusesStore().setLoading({
timeline: this.timelineName,
value: true,
})
timelineFetcher
.fetchAndUpdate({
store,
credentials,
timeline: this.timelineName,
older: true,
@ -237,7 +239,7 @@ const Timeline = {
}
})
.finally(() =>
store.commit('setLoading', {
useStatusesStore().setLoading({
timeline: this.timelineName,
value: false,
}),

View file

@ -90,7 +90,7 @@
:status-id="status.id"
:in-profile="inProfile"
:profile-user-id="userId"
:virtual-hidden="virtualScrollingEnabled && !statusesToDisplay.includes(status.id)"
:virtual-hidden="virtualScrollingEnabled && !statusesToDisplay.has(status.id)"
collapsable
/>
</div>

View file

@ -9,6 +9,7 @@ import UserCard from 'src/components/user_card/user_card.vue'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -106,7 +107,9 @@ const UserProfile = {
const startFetchingTimeline = (timeline, userId) => {
// Clear timeline only if load another user's profile
if (userId !== this.$store.state.statuses.timelines[timeline].userId) {
this.$store.commit('clearTimeline', { timeline })
useStatusesStore().clearTimeline({ timeline: 'user' })
useStatusesStore().clearTimeline({ timeline: 'userPinned' })
useStatusesStore().clearTimeline({ timeline: 'media' })
}
this.$store.dispatch('startFetchingTimeline', { timeline, userId })
}

View file

@ -8,6 +8,7 @@ import { useInterfaceStore } from 'src/stores/interface.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useShoutStore } from 'src/stores/shout.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { fetchTimeline } from 'src/api/timelines.js'
import {
@ -123,14 +124,16 @@ const api = {
data: message.notification,
})
} else if (message.event === 'update') {
dispatch('addNewStatuses', {
useStatusesStore().addNewStatuses({
timestamp: Date.now(),
statuses: [message.status],
userId: false,
showImmediately: timelineData.visibleStatuses.length === 0,
timeline: 'friends',
})
} else if (message.event === 'status.update') {
dispatch('addNewStatuses', {
useStatusesStore().addNewStatuses({
timestamp: Date.now(),
statuses: [message.status],
userId: false,
showImmediately:

View file

@ -1,10 +1,8 @@
import api from './api.js'
import drafts from './drafts.js'
import profileConfig from './profileConfig.js'
import statuses from './statuses.js'
export default {
statuses,
api,
profileConfig,
drafts,

View file

@ -1,906 +0,0 @@
import {
each,
find,
findIndex,
first,
last,
maxBy,
merge,
minBy,
omitBy,
remove,
} from 'lodash'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useUsersStore } from 'src/stores/users.js'
import {
fetchEmojiReactions,
fetchFavoritedByUsers,
fetchPinnedStatuses,
fetchRebloggedByUsers,
fetchScrobbles,
fetchStatus,
fetchStatusHistory,
fetchStatusSource,
search2,
} from 'src/api/public.js'
import {
bookmarkStatus,
deleteStatus,
favorite,
muteConversation,
pinOwnStatus,
reactWithEmoji,
retweet,
unbookmarkStatus,
unfavorite,
unmuteConversation,
unpinOwnStatus,
unreactWithEmoji,
unretweet,
} from 'src/api/user.js'
const emptyTl = (userId = 0) => ({
statuses: [],
statusesObject: {},
faves: [],
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: '',
minId: '',
minVisibleId: 0,
loading: false,
followers: [],
friends: [],
userId,
flushMarker: 0,
})
export const defaultState = () => ({
allStatuses: [],
scrobblesNextFetch: {},
allStatusesObject: {},
conversationsObject: {},
maxId: '',
favorites: new Set(),
timelines: {
mentions: emptyTl(),
public: emptyTl(),
user: emptyTl(),
favorites: emptyTl(),
media: emptyTl(),
publicAndExternal: emptyTl(),
friends: emptyTl(),
tag: emptyTl(),
dms: emptyTl(),
bookmarks: emptyTl(),
list: emptyTl(),
bubble: emptyTl(),
},
})
export const prepareStatus = (status) => {
// Set deleted flag
status.deleted = false
// To make the array reactive
status.attachments = status.attachments || []
return status
}
const mergeOrAdd = (arr, obj, item) => {
const oldItem = obj[item.id]
if (oldItem) {
// We already have this, so only merge the new info.
// We ignore null values to avoid overwriting existing properties with missing data
// we also skip 'user' because that is handled by users module
merge(
oldItem,
omitBy(item, (v, k) => v === null || k === 'user'),
)
// Reactivity fix.
oldItem.attachments.splice(oldItem.attachments.length)
return { item: oldItem, new: false }
} else {
// This is a new item, prepare it
prepareStatus(item)
arr.push(item)
obj[item.id] = item
return { item, new: true }
}
}
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)
const isSeqA = !Number.isNaN(seqA)
const isSeqB = !Number.isNaN(seqB)
if (isSeqA && isSeqB) {
return seqA > seqB ? -1 : 1
} else if (isSeqA && !isSeqB) {
return 1
} else if (!isSeqA && isSeqB) {
return -1
} else {
return a.id > b.id ? -1 : 1
}
}
const sortTimeline = (timeline) => {
timeline.visibleStatuses = timeline.visibleStatuses.sort(sortById)
timeline.statuses = timeline.statuses.sort(sortById)
timeline.minVisibleId = last(timeline.visibleStatuses)?.id
return timeline
}
const getLatestScrobble = (state, user) => {
const scrobblesSupport =
useInstanceCapabilitiesStore().pleromaScrobblesAvailable
if (!scrobblesSupport || !user.name || user.id === 'undefined') {
return
}
if (
state.scrobblesNextFetch[user.id] &&
state.scrobblesNextFetch[user.id] > Date.now()
) {
return
}
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
if (!scrobblesSupport) return
fetchScrobbles({ accountId: user.id })
.then(({ data: scrobbles }) => {
if (scrobbles?.error) {
useInstanceCapabilitiesStore().set('pleromaScrobblesAvailable', false)
return
}
if (scrobbles.length > 0) {
user.latestScrobble = scrobbles[0]
state.scrobblesNextFetch[user.id] = Date.now() + 60 * 1000
}
})
.catch((e) => {
console.warn('cannot fetch scrobbles', e)
})
}
// Add status to the global storages (arrays and objects maintaining statuses) except timelines
const addStatusToGlobalStorage = (state, data) => {
getLatestScrobble(state, data.user)
const result = mergeOrAdd(state.allStatuses, state.allStatusesObject, data)
if (result.new) {
// Add to conversation
const status = result.item
const conversationsObject = state.conversationsObject
const conversationId = status.statusnet_conversation_id
if (conversationsObject[conversationId]) {
conversationsObject[conversationId].push(status)
} else {
conversationsObject[conversationId] = [status]
}
}
return result
}
const addNewStatuses = (
state,
{
statuses,
showImmediately = false,
timeline,
user = {},
noIdUpdate = false,
userId,
pagination = {},
},
) => {
// Sanity check
if (!Array.isArray(statuses)) {
return false
}
const allStatuses = state.allStatuses
const timelineObject = state.timelines[timeline]
// Mismatch between API pagination and our internal minId/maxId tracking systems:
// pagination.maxId is the oldest of the returned statuses when fetching older,
// and pagination.minId is the newest when fetching newer. The names come directly
// from the arguments they're supposed to be passed as for the next fetch.
const minNew =
pagination.maxId || (statuses.length > 0 ? minBy(statuses, 'id').id : 0)
const maxNew =
pagination.minId || (statuses.length > 0 ? maxBy(statuses, 'id').id : 0)
const newer =
timeline &&
(maxNew > timelineObject.maxId || timelineObject.maxId === '') &&
statuses.length > 0
const older =
timeline &&
(minNew < timelineObject.minId || timelineObject.minId === '') &&
statuses.length > 0
if (!noIdUpdate && newer) {
timelineObject.maxId = maxNew
}
if (!noIdUpdate && older) {
timelineObject.minId = minNew
}
// 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
if (
(timeline === 'user' || timeline === 'media') &&
timelineObject.userId !== userId
) {
return
}
const addStatus = (data, showImmediately, addToTimeline = true) => {
const result = addStatusToGlobalStorage(state, data)
const status = result.item
if (result.new) {
// We are mentioned in a post
if (
status.type === 'status' &&
find(status.attentions, { id: user.id })
) {
const mentions = state.timelines.mentions
// Add the mention to the mentions timeline
if (timelineObject !== mentions) {
mergeOrAdd(mentions.statuses, mentions.statusesObject, status)
mentions.newStatusCount += 1
sortTimeline(mentions)
}
}
if (status.visibility === 'direct') {
const dms = state.timelines.dms
mergeOrAdd(dms.statuses, dms.statusesObject, status)
dms.newStatusCount += 1
sortTimeline(dms)
}
}
// Decide if we should treat the status as new for this timeline.
let resultForCurrentTimeline
// Some statuses should only be added to the global status repository.
if (timeline && addToTimeline) {
resultForCurrentTimeline = mergeOrAdd(
timelineObject.statuses,
timelineObject.statusesObject,
status,
)
}
if (timeline && showImmediately) {
// Add it directly to the visibleStatuses, don't change
// newStatusCount
mergeOrAdd(
timelineObject.visibleStatuses,
timelineObject.visibleStatusesObject,
status,
)
} else if (timeline && addToTimeline && resultForCurrentTimeline.new) {
// Just change newStatuscount
timelineObject.newStatusCount += 1
}
if (status.quote) {
addStatus(
status.quote,
/* showImmediately = */ false,
/* addToTimeline = */ false,
)
}
return status
}
const favoriteStatus = (favorite) => {
const status = find(allStatuses, { id: favorite.in_reply_to_status_id })
if (status) {
// This is our favorite, so the relevant bit.
if (favorite.user.id === user.id) {
status.favorited = true
} else {
status.fave_num += 1
}
}
return status
}
const processors = {
status: (status) => {
addStatus(status, showImmediately)
},
edit: (status) => {
addStatus(status, showImmediately)
},
retweet: (status) => {
// RetweetedStatuses are never shown immediately
const retweetedStatus = addStatus(status.retweeted_status, false, false)
let retweet
// If the retweeted status is already there, don't add the retweet
// to the timeline.
if (
timeline &&
find(timelineObject.statuses, (s) => {
if (s.retweeted_status) {
return (
s.id === retweetedStatus.id ||
s.retweeted_status.id === retweetedStatus.id
)
} else {
return s.id === retweetedStatus.id
}
})
) {
// Already have it visible (either as the original or another RT), don't add to timeline, don't show.
retweet = addStatus(status, false, false)
} else {
retweet = addStatus(status, showImmediately)
}
retweet.retweeted_status = retweetedStatus
},
favorite: (favorite) => {
// Only update if this is a new favorite.
// Ignore our own favorites because we get info about likes as response to like request
if (!state.favorites.has(favorite.id)) {
state.favorites.add(favorite.id)
favoriteStatus(favorite)
}
},
follow: () => {
// NOOP, it is known status but we don't do anything about it for now
},
default: (unknown) => {
console.warn('unknown status type', unknown)
},
}
each(statuses, (status) => {
const type = status.type
const processor = processors[type] || processors.default
processor(status)
})
// Keep the visible statuses sorted
if (timeline && !(timeline === 'bookmarks')) {
sortTimeline(timelineObject)
}
}
const removeStatus = (state, { timeline, userId }) => {
const timelineObject = state.timelines[timeline]
if (userId) {
remove(timelineObject.statuses, { user: { id: userId } })
remove(timelineObject.visibleStatuses, { user: { id: userId } })
timelineObject.minVisibleId =
timelineObject.visibleStatuses.length > 0
? last(timelineObject.visibleStatuses).id
: 0
timelineObject.maxId =
timelineObject.statuses.length > 0 ? first(timelineObject.statuses).id : 0
}
}
export const mutations = {
addNewStatuses,
removeStatus,
showNewStatuses(state, { timeline }) {
const oldTimeline = state.timelines[timeline]
oldTimeline.newStatusCount = 0
oldTimeline.visibleStatuses = oldTimeline.statuses.slice(0, 50)
oldTimeline.minVisibleId = last(oldTimeline.visibleStatuses).id
oldTimeline.minId = oldTimeline.minVisibleId
oldTimeline.visibleStatusesObject = {}
each(oldTimeline.visibleStatuses, (status) => {
oldTimeline.visibleStatusesObject[status.id] = status
})
},
resetStatuses(state) {
const emptyState = defaultState()
Object.entries(emptyState).forEach(([key, value]) => {
state[key] = value
})
},
clearTimeline(state, { timeline, excludeUserId = false }) {
const userId = excludeUserId ? state.timelines[timeline].userId : undefined
state.timelines[timeline] = emptyTl(userId)
},
setFavorited(state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
if (newStatus.favorited !== value) {
if (value) {
newStatus.fave_num++
} else {
newStatus.fave_num--
}
}
newStatus.favorited = value
},
setFavoritedConfirm(state, { status, user }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.favorited = status.favorited
newStatus.fave_num = status.fave_num
const index = findIndex(newStatus.favoritedBy, { id: user.id })
if (index !== -1 && !newStatus.favorited) {
newStatus.favoritedBy.splice(index, 1)
} else if (index === -1 && newStatus.favorited) {
newStatus.favoritedBy.push(user)
}
},
setMutedStatus(state, status) {
const newStatus = state.allStatusesObject[status.id]
newStatus.thread_muted = status.thread_muted
if (newStatus.thread_muted !== undefined) {
state.conversationsObject[newStatus.statusnet_conversation_id].forEach(
(status) => {
status.thread_muted = newStatus.thread_muted
},
)
}
},
setRetweeted(state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
if (newStatus.repeated !== value) {
if (value) {
newStatus.repeat_num++
} else {
newStatus.repeat_num--
}
}
newStatus.repeated = value
},
setRetweetedConfirm(state, { status, user }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.repeated = status.repeated
newStatus.repeat_num = status.repeat_num
const index = findIndex(newStatus.rebloggedBy, { id: user.id })
if (index !== -1 && !newStatus.repeated) {
newStatus.rebloggedBy.splice(index, 1)
} else if (index === -1 && newStatus.repeated) {
newStatus.rebloggedBy.push(user)
}
},
setBookmarked(state, { status, value }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.bookmarked = value
newStatus.bookmark_folder_id = status.bookmark_folder_id
},
setBookmarkedConfirm(state, { status }) {
const newStatus = state.allStatusesObject[status.id]
newStatus.bookmarked = status.bookmarked
if (status.pleroma)
newStatus.bookmark_folder_id = status.pleroma.bookmark_folder
},
setDeleted(state, { status }) {
const newStatus = state.allStatusesObject[status.id]
if (newStatus) newStatus.deleted = true
},
setManyDeleted(state, condition) {
Object.values(state.allStatusesObject).forEach((status) => {
if (condition(status)) {
status.deleted = true
}
})
},
setLoading(state, { timeline, value }) {
state.timelines[timeline].loading = value
},
setNsfw(state, { id, nsfw }) {
const newStatus = state.allStatusesObject[id]
newStatus.nsfw = nsfw
},
queueFlush(state, { timeline, id }) {
state.timelines[timeline].flushMarker = id
},
queueFlushAll(state) {
Object.keys(state.timelines).forEach((timeline) => {
state.timelines[timeline].flushMarker = state.timelines[timeline].maxId
})
},
addRepeats(state, { id, rebloggedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[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,
)
},
addFavs(state, { id, favoritedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[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,
)
},
addEmojiReactionsBy(state, { id, emojiReactions }) {
const status = state.allStatusesObject[id]
status.emoji_reactions = emojiReactions
},
addOwnReaction(state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
const reaction = status.emoji_reactions[reactionIndex] || {
name: emoji,
count: 0,
accounts: [],
}
const newReaction = {
...reaction,
count: reaction.count + 1,
me: true,
accounts: [...reaction.accounts, currentUser],
}
// Update count of existing reaction if it exists, otherwise append at the end
if (reactionIndex >= 0) {
status.emoji_reactions[reactionIndex] = newReaction
} else {
status.emoji_reactions = [...status.emoji_reactions, newReaction]
}
},
removeOwnReaction(state, { id, emoji, currentUser }) {
const status = state.allStatusesObject[id]
const reactionIndex = findIndex(status.emoji_reactions, { name: emoji })
if (reactionIndex < 0) return
const reaction = status.emoji_reactions[reactionIndex]
const accounts = reaction.accounts || []
const newReaction = {
...reaction,
count: reaction.count - 1,
me: false,
accounts: accounts.filter((acc) => acc.id !== currentUser.id),
}
if (newReaction.count > 0) {
status.emoji_reactions[reactionIndex] = newReaction
} else {
status.emoji_reactions = status.emoji_reactions.filter(
(r) => r.name !== emoji,
)
}
},
updateStatusWithPoll(state, { id, poll }) {
const status = state.allStatusesObject[id]
status.poll = poll
},
setVirtualHeight(state, { statusId, height }) {
state.allStatusesObject[statusId].virtualHeight = height
},
}
const statuses = {
state: defaultState(),
actions: {
addNewStatuses(
{ rootState, commit },
{
statuses,
showImmediately = false,
timeline = false,
noIdUpdate = false,
userId,
pagination,
},
) {
return commit('addNewStatuses', {
statuses,
showImmediately,
timeline,
noIdUpdate,
user: useUsersStore().currentUser,
userId,
pagination,
})
},
fetchStatus({ rootState, dispatch }, id) {
return fetchStatus({ id }).then(({ data: status }) =>
dispatch('addNewStatuses', { statuses: [status] }),
)
},
fetchStatusSource({ rootState }, status) {
return fetchStatusSource({
id: status.id,
credentials: useOAuthStore().token,
}).then(({ data }) => data)
},
fetchStatusHistory(_, status) {
return fetchStatusHistory({ status }).then(({ data }) => data)
},
deleteStatus({ rootState, commit }, status) {
deleteStatus({
id: status.id,
credentials: useOAuthStore().token,
})
.then(() => {
commit('setDeleted', { status })
})
.catch((e) => {
useInterfaceStore().pushGlobalNotice({
level: 'error',
messageKey: 'status.delete_error',
messageArgs: [e.message],
timeout: 5000,
})
})
},
deleteStatusById({ rootState, commit }, id) {
const status = rootState.statuses.allStatusesObject[id]
commit('setDeleted', { status })
},
markStatusesAsDeleted({ commit }, condition) {
commit('setManyDeleted', condition)
},
favorite({ rootState, commit }, status) {
// Optimistic favoriting...
commit('setFavorited', { status, value: true })
favorite({
id: status.id,
credentials: useOAuthStore().token,
}).then(({ data: status }) =>
commit('setFavoritedConfirm', {
status,
user: useUsersStore().currentUser,
}),
)
},
unfavorite({ rootState, commit }, status) {
// Optimistic unfavoriting...
commit('setFavorited', { status, value: false })
unfavorite({
id: status.id,
credentials: useOAuthStore().token,
}).then(({ data: status }) =>
commit('setFavoritedConfirm', {
status,
user: useUsersStore().currentUser,
}),
)
},
fetchPinnedStatuses({ rootState, dispatch }, userId) {
fetchPinnedStatuses({
id: userId,
credentials: useOAuthStore().token,
}).then(({ data: statuses }) =>
dispatch('addNewStatuses', {
statuses,
timeline: 'user',
userId,
showImmediately: true,
noIdUpdate: true,
}),
)
},
pinStatus({ rootState, dispatch }, statusId) {
return pinOwnStatus({
id: statusId,
credentials: useOAuthStore().token,
}).then(({ data: status }) =>
dispatch('addNewStatuses', { statuses: [status] }),
)
},
unpinStatus({ rootState, dispatch }, statusId) {
return unpinOwnStatus({
id: statusId,
credentials: useOAuthStore().token,
}).then(({ data: status }) =>
dispatch('addNewStatuses', { statuses: [status] }),
)
},
muteConversation({ rootState, commit }, { id: statusId }) {
return muteConversation({
id: statusId,
credentials: useOAuthStore().token,
}).then(({ data: status }) => commit('setMutedStatus', status))
},
unmuteConversation({ rootState, commit }, { id: statusId }) {
return unmuteConversation({
id: statusId,
credentials: useOAuthStore().token,
}).then(({ data: status }) => commit('setMutedStatus', status))
},
retweet({ rootState, commit }, status) {
// Optimistic retweeting...
commit('setRetweeted', { status, value: true })
retweet({
id: status.id,
credentials: useOAuthStore().token,
}).then(({ data: status }) =>
commit('setRetweetedConfirm', {
status: status.retweeted_status,
user: useUsersStore().currentUser,
}),
)
},
unretweet({ rootState, commit }, status) {
// Optimistic unretweeting...
commit('setRetweeted', { status, value: false })
unretweet({
id: status.id,
credentials: useOAuthStore().token,
}).then(({ data: status }) =>
commit('setRetweetedConfirm', {
status,
user: useUsersStore().currentUser,
}),
)
},
bookmark({ rootState, commit }, status) {
commit('setBookmarked', { status, value: true })
bookmarkStatus({
id: status.id,
folder_id: status.bookmark_folder_id,
credentials: useOAuthStore().token,
}).then(({ data: status }) => {
commit('setBookmarkedConfirm', { status })
})
},
unbookmark({ rootState, commit }, status) {
commit('setBookmarked', { status, value: false })
unbookmarkStatus({
id: status.id,
credentials: useOAuthStore().token,
}).then(({ data: status }) => {
commit('setBookmarkedConfirm', { status })
})
},
queueFlush({ commit }, { timeline, id }) {
commit('queueFlush', { timeline, id })
},
queueFlushAll({ commit }) {
commit('queueFlushAll')
},
fetchFavsAndRepeats({ rootState, commit }, id) {
Promise.all([
fetchFavoritedByUsers({
id,
credentials: useOAuthStore().token,
}).then(({ data }) => data),
fetchRebloggedByUsers({
id,
credentials: useOAuthStore().token,
}).then(({ data }) => data),
]).then(([favoritedByUsers, rebloggedByUsers]) => {
commit('addFavs', {
id,
favoritedByUsers,
currentUser: useUsersStore().currentUser,
})
commit('addRepeats', {
id,
rebloggedByUsers,
currentUser: useUsersStore().currentUser,
})
})
},
reactWithEmoji({ rootState, dispatch, commit }, { id, emoji }) {
const currentUser = useUsersStore().currentUser
if (!currentUser) return
commit('addOwnReaction', { id, emoji, currentUser })
reactWithEmoji({
id,
emoji,
credentials: useOAuthStore().token,
}).then(() => {
dispatch('fetchEmojiReactionsBy', id)
})
},
unreactWithEmoji({ rootState, dispatch, commit }, { id, emoji }) {
const currentUser = useUsersStore().currentUser
if (!currentUser) return
commit('removeOwnReaction', { id, emoji, currentUser })
unreactWithEmoji({
id,
emoji,
currentUser: useUsersStore().currentUser,
}).then(() => {
dispatch('fetchEmojiReactionsBy', id)
})
},
fetchEmojiReactionsBy({ rootState, commit }, id) {
return fetchEmojiReactions({
id,
credentials: useOAuthStore().token,
}).then(({ data: emojiReactions }) => {
commit('addEmojiReactionsBy', {
id,
emojiReactions,
currentUser: useUsersStore().currentUser,
})
})
},
fetchFavs({ rootState, commit }, id) {
fetchFavoritedByUsers({
id,
credentials: useOAuthStore().token,
}).then(({ data: favoritedByUsers }) =>
commit('addFavs', {
id,
favoritedByUsers,
currentUser: useUsersStore().currentUser,
}),
)
},
fetchRepeats({ rootState, commit }, id) {
fetchRebloggedByUsers({
id,
credentials: useOAuthStore().token,
}).then(({ data: rebloggedByUsers }) =>
commit('addRepeats', {
id,
rebloggedByUsers,
currentUser: useUsersStore().currentUser,
}),
)
},
search(store, { q, resolve, limit, offset, following, type }) {
return search2({
q,
resolve,
limit,
offset,
following,
type,
credentials: useOAuthStore().token,
}).then((result) => {
const { data, ...rest } = result
useUsersStore().addNewUsers({
...rest,
data: data.accounts,
})
useUsersStore().addNewUsers({
...rest,
data: data.statuses.map((s) => s.user).filter(Boolean),
})
store.commit('addNewStatuses', {
statuses: data.statuses,
})
data.statuses = data.statuses.map(
(s) => store.state.allStatusesObject[s.id],
)
return data
})
},
setVirtualHeight({ commit }, { statusId, height }) {
commit('setVirtualHeight', { statusId, height })
},
},
mutations,
}
export default statuses

View file

@ -1,5 +1,6 @@
import { map } from 'lodash'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import {
@ -38,9 +39,10 @@ const postStatus = ({
poll,
preview,
idempotencyKey,
}).then(({ data }) => {
}).then(({ data, timestamp }) => {
if (!preview)
store.dispatch('addNewStatuses', {
useStatusesStore().addNewStatuses({
timestamp,
statuses: [data],
timeline: 'friends',
showImmediately: true,

View file

@ -5,12 +5,12 @@ import { promiseInterval } from '../promise_interval/promise_interval.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
import { fetchTimeline } from 'src/api/timelines.js'
const update = ({
store,
statuses,
timeline,
showImmediately,
@ -20,8 +20,8 @@ const update = ({
}) => {
const ccTimeline = camelCase(timeline)
store.dispatch('addNewStatuses', {
timeline: ccTimeline,
useStatusesStore().addNewStatuses({
timelineName: ccTimeline,
userId,
listId,
statuses,
@ -31,7 +31,6 @@ const update = ({
}
const fetchAndUpdate = ({
store,
credentials,
timeline = 'friends',
older = false,
@ -45,8 +44,7 @@ const fetchAndUpdate = ({
sinceId,
}) => {
const args = { timeline, credentials }
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
const timelineData = useStatusesStore().timelines[camelCase(timeline)]
const { hideMutedPosts, replyVisibility } =
useMergedConfigStore().mergedConfig
const loggedIn = !!useUsersStore().currentUser
@ -86,10 +84,9 @@ const fetchAndUpdate = ({
!timelineData.loading &&
numStatusesBeforeFetch > 0
) {
store.dispatch('queueFlush', { timeline, id: timelineData.maxId })
useStatusesStore().queueFlush({ timeline, id: timelineData.maxId })
}
update({
store,
statuses,
timeline,
showImmediately,
@ -116,23 +113,21 @@ const fetchAndUpdate = ({
const startFetching = ({
timeline = 'friends',
credentials,
store,
userId,
listId,
statusId,
bookmarkFolderId,
tag,
}) => {
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
const showImmediately = timelineData.visibleStatuses.length === 0
const timelineData = useStatusesStore().timelines[camelCase(timeline)]
const showImmediately = timelineData.visibleStatuses.size === 0
console.log(timeline)
timelineData.userId = userId
timelineData.listId = listId
timelineData.bookmarkFolderId = bookmarkFolderId
fetchAndUpdate({
timeline,
credentials,
store,
showImmediately,
userId,
listId,
@ -144,7 +139,6 @@ const startFetching = ({
fetchAndUpdate({
timeline,
credentials,
store,
userId,
listId,
statusId,

View file

@ -2,6 +2,7 @@ import { cloneDeep, differenceWith, get, isEqual, set } from 'lodash'
import { defineStore } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import {
addNewEmojiFile,
@ -362,6 +363,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
async fetchStatuses(opts) {
const {
data: { total, activities },
timestamp,
} = await listStatuses({
credentials: useOAuthStore().token,
opts,
@ -369,7 +371,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
const statuses = activities.map(parseStatus)
await window.vuex.dispatch('addNewStatuses', { statuses })
useStatusesStore().addNewStatuses({ statuses, timestamp })
return {
items: statuses,
@ -377,13 +379,13 @@ export const useAdminSettingsStore = defineStore('adminSettings', {
}
},
async changeStatusScope(opts) {
const { data } = await changeStatusScope({
const { data, timestamp } = await changeStatusScope({
credentials: useOAuthStore().token,
opts,
})
const status = parseStatus(data)
await window.vuex.dispatch('addNewStatuses', { statuses: [status] })
useStatusesStore().addNewStatuses({ statuses: [status], timestamp })
},
// Users stuff

View file

@ -14,6 +14,7 @@ import { useI18nStore } from 'src/stores/i18n.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useReportsStore } from 'src/stores/reports.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUsersStore } from 'src/stores/users.js'
@ -80,7 +81,7 @@ export const useNotificationsStore = defineStore('notifications', {
)
// Synchronous commit to add all the statuses
window.vuex.commit('addNewStatuses', {
useStatusesStore().addNewStatuses({
timestamp,
statuses: statusNotifications.map(
(notification) => notification.status,
@ -90,7 +91,7 @@ export const useNotificationsStore = defineStore('notifications', {
// Update references to statuses in notifications to ones in the store
statusNotifications.forEach((notification) => {
const id = notification.status.id
const referenceStatus = window.vuex.state.statuses.allStatusesObject[id]
const referenceStatus = useStatusesStore().allStatuses.get(id)
if (referenceStatus) {
notification.status = referenceStatus
@ -103,7 +104,7 @@ export const useNotificationsStore = defineStore('notifications', {
}
if (notification.type === 'pleroma:emoji_reaction') {
window.vuex.dispatch('fetchEmojiReactionsBy', notification.status.id)
useStatusesStore().fetchEmojiReactionsBy(notification.status.id)
}
// Only add a new notification if we don't have one for the same action

View file

@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
import { useInterfaceStore } from 'src/stores/interface.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { setReportState } from 'src/api/admin.js'
@ -18,8 +19,8 @@ export const useReportsStore = defineStore('reports', {
}),
actions: {
openUserReportingModal({ userId, statusIds = [] }) {
const preTickedStatuses = statusIds.map(
(id) => window.vuex.state.statuses.allStatusesObject[id],
const preTickedStatuses = statusIds.map((id) =>
useStatusesStore().allStatuses.get(id),
)
const preTickedIds = statusIds
const statuses = preTickedStatuses.concat(

View file

@ -1,4 +1,4 @@
import { each, first, last, maxBy, merge, minBy, omitBy, remove } from 'lodash'
import { first, last, maxBy, minBy } from 'lodash'
import { defineStore } from 'pinia'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
@ -33,7 +33,7 @@ import {
unretweet,
} from 'src/api/user.js'
const emptyTl = () => ({
const emptyTl = (userId) => ({
statuses: new Map(),
faves: [],
visibleStatuses: new Map(),
@ -71,49 +71,6 @@ export const defaultState = () => ({
},
})
const mergeOrAdd = (map, status, timestamp) => {
const existing = map.get(status.id)
const oldTimestamp = this.timestamps.get(existing)
const { user: unused0, ...old } = existing ?? {}
const { user: statusUser, ...neu } = status
const [user] = useUsersStore().addNewUsers({ data: statusUser, timestamp })
existing.user = user
// implicit: if oldTimestamp is undefined this will still be false
if (oldTimestamp > timestamp) return [existing, false] // not overwriting old data with new
const newStatus = {
...old,
...neu,
user,
}
map.set(item.id, item)
this.timestamps.set(newStatus, timestamp)
return [map.get(item.id), true]
}
const sortById = (a, b) => {
const seqA = Number(a.id)
const seqB = Number(b.id)
const isSeqA = !Number.isNaN(seqA)
const isSeqB = !Number.isNaN(seqB)
if (isSeqA && isSeqB) {
return seqA > seqB ? -1 : 1
} else if (isSeqA && !isSeqB) {
return 1
} else if (!isSeqA && isSeqB) {
return -1
} else {
return a.id > b.id ? -1 : 1
}
}
const getLatestScrobble = (user) => {
const scrobblesSupport =
useInstanceCapabilitiesStore().pleromaScrobblesAvailable
@ -149,7 +106,6 @@ const getLatestScrobble = (user) => {
})
}
const USER_TIMELINES = new Set(['user', 'userPinned', 'media'])
export const useStatusesStore = defineStore('statuses', {
@ -160,8 +116,8 @@ export const useStatusesStore = defineStore('statuses', {
showImmediately = false,
timelineName,
user = {},
userId,
noIdUpdate = false,
Id,
pagination = {},
timestamp,
}) {
@ -179,17 +135,14 @@ export const useStatusesStore = defineStore('statuses', {
// 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
if (
USER_TIMELINES.has(timelineName) &&
timeline.userId !== userId
) {
if (USER_TIMELINES.has(timelineName) && timeline.userId !== userId) {
return
}
const addStatus = (data, showImmediately, addToTimeline = true) => {
getLatestScrobble(data.user)
const [status] = mergeOrAdd(this.allStatuses, data)
const [status] = this.mergeOrAdd(this.allStatuses, data)
// Add to conversation
const conversations = this.conversations
@ -210,7 +163,7 @@ export const useStatusesStore = defineStore('statuses', {
// Add the mention to the mentions timeline
if (timeline !== mentions) {
const [, isNew] = mergeOrAdd(mentions.statuses, status)
const [, isNew] = this.mergeOrAdd(mentions.statuses, data)
if (isNew) mentions.newStatusCount += 1
}
}
@ -218,21 +171,23 @@ export const useStatusesStore = defineStore('statuses', {
if (status.visibility === 'direct') {
const dms = this.timelines.dms
const [, isNew] = mergeOrAdd(dms.statuses, status)
const [, isNew] = this.mergeOrAdd(dms.statuses, data)
if (isNew) dms.newStatusCount += 1
}
// Some statuses should only be added to the global status repository.
if (timeline && addToTimeline) {
// Decide if we should treat the status as new for this timeline.
const [status, isNew] = mergeOrAdd(timeline.statuses, status)
if (showImmediately) {
// Add it directly to the visibleStatuses, don't change
// newStatusCount
timeline.visibleStatuses.add(status.id, status)
} else {
// Just change newStatuscount
timeline.newStatusCount += 1
const [status, isNew] = this.mergeOrAdd(timeline.statuses, data)
if (isNew) {
if (showImmediately) {
// Add it directly to the visibleStatuses, don't change
// newStatusCount
timeline.visibleStatuses.set(status.id, status)
} else {
// Just change newStatuscount
timeline.newStatusCount += 1
}
}
}
@ -256,12 +211,17 @@ export const useStatusesStore = defineStore('statuses', {
},
retweet: (status) => {
// RetweetedStatuses are never shown immediately
const retweetedStatus = addStatus(status.retweeted_status, false, false)
const retweetedStatus = addStatus(
status.retweeted_status,
false,
false,
)
let retweet
// If the retweeted status is already there, don't add the retweet
// to the timeline.
if (timeline?.statuses.values().some((s) => {
if (
[...(timeline?.statuses.values() ?? [])].some((s) => {
if (s.retweeted_status) {
return (
s.id === retweetedStatus.id ||
@ -313,6 +273,35 @@ export const useStatusesStore = defineStore('statuses', {
processor(status)
})
},
mergeOrAdd(map, status, timestamp) {
const existing = map.get(status.id) ?? {}
const oldTimestamp = this.timestamps.get(existing)
const { user: unused0, ...old } = existing
const { user: statusUser, ...neu } = status
const [user] = useUsersStore().addNewUsers({
data: statusUser,
timestamp,
})
existing.user = user // reactive update in case we return old
// implicit: if oldTimestamp is undefined this will still be false
if (oldTimestamp > timestamp) return [existing, false] // not overwriting old data with new
const newStatus = {
...old,
...neu,
user,
}
map.set(newStatus.id, newStatus)
this.timestamps.set(newStatus, timestamp)
return [map.get(newStatus.id), true]
},
fetchStatus(id) {
return fetchStatus({ id }).then(({ data: status, timestamp }) =>
this.addNewStatuses({ statuses: [status], timestamp }),
@ -376,10 +365,7 @@ export const useStatusesStore = defineStore('statuses', {
)
},
fetchFavsAndRepeats(id) {
return Promise.all([
this.fetchFavs(id),
this.fetchRepeats(id),
])
return Promise.all([this.fetchFavs(id), this.fetchRepeats(id)])
},
// Updates
@ -414,8 +400,8 @@ export const useStatusesStore = defineStore('statuses', {
const minNew = pagination.maxId ?? minBy(statuses, 'id').id ?? ''
const maxNew = pagination.minId ?? maxBy(statuses, 'id').id ?? ''
const newer = (maxNew > timeline.maxId || timeline.maxId === '')
const older = (minNew < timeline.minId || timeline.minId === '')
const newer = maxNew > timeline.maxId || timeline.maxId === ''
const older = minNew < timeline.minId || timeline.minId === ''
if (newer) {
timeline.maxId = maxNew
@ -429,8 +415,10 @@ export const useStatusesStore = defineStore('statuses', {
timeline.newStatusCount = 0
timeline.visibleStatuses = new Map([...timeline.statuses.entries()].slice(0, 50))
timeline.minVisibleId = last(timeline.visibleStatuses).id
timeline.visibleStatuses = new Map(
[...timeline.statuses.entries()].slice(0, 50),
)
timeline.minVisibleId = last(timeline.visibleStatuses.keys())
timeline.minId = ''
timeline.maxId = ''
this.updateTimelineExtremes(timeline, [...timeline.statuses.values()])
@ -442,9 +430,9 @@ export const useStatusesStore = defineStore('statuses', {
this[key] = value
})
},
clearTimeline(state, { timeline, excludeUserId = false }) {
const userId = excludeUserId ? state.timelines[timeline].userId : undefined
state.timelines[timeline] = emptyTl(userId)
clearTimeline({ timeline, excludeUserId = false }) {
const userId = excludeUserId ? this.timelines[timeline].userId : undefined
this.timelines[timeline] = emptyTl(userId)
},
queueFlush({ timeline, id }) {
this.timelines[timeline].flushMarker = id
@ -650,8 +638,8 @@ export const useStatusesStore = defineStore('statuses', {
},
setBookmarked({ id, value, bookmark_folder_id }) {
const status = this.allStatuses.get(id)
newStatus.bookmarked = value
newStatus.bookmark_folder_id = value ? bookmark_folder_id : null
status.bookmarked = value
status.bookmark_folder_id = value ? bookmark_folder_id : null
},
/// Mute
@ -659,25 +647,29 @@ export const useStatusesStore = defineStore('statuses', {
return muteConversation({
id,
credentials: useOAuthStore().token,
}).then(({ data: status, timestamp }) => {
this.addNewStatuses({
statuses: [status],
timestamp,
})
.then(({ data: status, timestamp }) => {
this.addNewStatuses({
statuses: [status],
timestamp,
})
return status
})
return status
}).then((status) => this.setMutedStatus(status))
.then((status) => this.setMutedStatus(status))
},
unmuteConversation(id) {
return unmuteConversation({
id,
credentials: useOAuthStore().token,
}).then(({ data: status, timestamp }) => {
this.addNewStatuses({
statuses: [status],
timestamp,
})
.then(({ data: status, timestamp }) => {
this.addNewStatuses({
statuses: [status],
timestamp,
})
return status
})
return status
}).then((status) => this.setMutedStatus(status))
.then((status) => this.setMutedStatus(status))
},
setMutedStatus({ id, thread_muted }) {
// Setting thread_muted flag on all other known statuses
@ -686,11 +678,11 @@ export const useStatusesStore = defineStore('statuses', {
newStatus.thread_muted = thread_muted
if (newStatus.thread_muted !== undefined) {
state.conversations.get(newStatus.statusnet_conversation_id).forEach(
(status) => {
this.conversations
.get(newStatus.statusnet_conversation_id)
.forEach((status) => {
status.thread_muted = thread_muted
},
)
})
}
},
@ -774,9 +766,7 @@ export const useStatusesStore = defineStore('statuses', {
statuses: data.statuses,
})
data.statuses = data.statuses.map(
(s) => this.allStatuses.get(s.id),
)
data.statuses = data.statuses.map((s) => this.allStatuses.get(s.id))
return data
})
},
@ -785,8 +775,7 @@ export const useStatusesStore = defineStore('statuses', {
removeUserStatuses({ timelineName, userId }) {
const timeline = this.timelines[timelineName]
timeline
.statuses
timeline.statuses
.values()
.filter(({ user }) => user.id === userId)
.forEach(({ id }) => {
@ -795,8 +784,8 @@ export const useStatusesStore = defineStore('statuses', {
})
timeline.minVisibleId =
timeline.visibleStatuses.length > 0
? last(timeline.visibleStatuses).id
: 0
? last(timeline.visibleStatuses).id
: 0
timeline.maxId =
timeline.statuses.length > 0 ? first(timeline.statuses).id : 0
},
@ -807,8 +796,8 @@ export const useStatusesStore = defineStore('statuses', {
const status = this.allStatuses.get(id)
status.poll = poll
},
setLoading(state, { timeline, value }) {
state.timelines[timeline].loading = value
setLoading({ timeline, value }) {
this.timelines[timeline].loading = value
},
},
})

View file

@ -20,7 +20,9 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j
import { useInterfaceStore } from 'src/stores/interface.js'
import { useListsStore } from 'src/stores/lists.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useNotificationsStore } from 'src/stores/notifications.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
@ -144,12 +146,12 @@ export const useUsersStore = defineStore('users', {
const { data, timestamp } = response
const users = Array.isArray(data) ? data : [data]
users.forEach((user) => {
return users.map((user) => {
const existing = this.users.get(user.id) ?? {}
const oldTimestamp = this.timestamps.get(existing)
// implicit: if oldTimestamp is undefined this will still be false
if (oldTimestamp > timestamp) return // not overwriting old data with new
if (oldTimestamp > timestamp) return existing // not overwriting old data with new
const { relationship: unused0, ...old } = existing
const { relationship: unused1, ...neu } = user
@ -163,6 +165,8 @@ export const useUsersStore = defineStore('users', {
if (user.id === this.currentUser.id) {
this.currentUser = newUser
}
return this.users.get(user.id)
})
},
updateUserRelationship(relationships) {
@ -594,7 +598,7 @@ export const useUsersStore = defineStore('users', {
useBookmarkFoldersStore().stopFetching()
store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications')
store.commit('resetStatuses')
useStatusesStore().resetStatuses()
useChatsStore().resetChats()
oauth.clearToken()
Cookies.remove('__Host-pleroma_key', { path: '/' })

View file

@ -6,6 +6,7 @@ import { mountOpts } from '../../../fixtures/setup_test'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useMergedConfigStore } from 'src/stores/merged_config.js'
import { useStatusesStore } from 'src/stores/statuses.js'
import { useUsersStore } from 'src/stores/users.js'
const currentUser = {
@ -37,7 +38,7 @@ const replyMountOpts = (props) =>
props,
afterStore(store) {
useUsersStore().currentUser = currentUser
store.state.statuses.allStatusesObject = {
useStatusesStore().allStatuses = {
[repliedStatus.id]: repliedStatus,
}
},