diff --git a/src/boot/routes.js b/src/boot/routes.js index 6956d0140..3b21d874d 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -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', diff --git a/src/components/bookmark_timeline/bookmark_timeline.js b/src/components/bookmark_timeline/bookmark_timeline.js index bf633c18c..dbda08ba1 100644 --- a/src/components/bookmark_timeline/bookmark_timeline.js +++ b/src/components/bookmark_timeline/bookmark_timeline.js @@ -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') }, } diff --git a/src/components/bubble_timeline/bubble_timeline.vue b/src/components/bubble_timeline/bubble_timeline.vue deleted file mode 100644 index 4aefa2729..000000000 --- a/src/components/bubble_timeline/bubble_timeline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/components/chat_message/chat_message.js b/src/components/chat_message/chat_message.js index 0428b404e..5d086aade 100644 --- a/src/components/chat_message/chat_message.js +++ b/src/components/chat_message/chat_message.js @@ -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) { diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index 1998c9453..498ddbd84 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -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', diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index ea5637944..4d51b8461 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -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', diff --git a/src/components/dm_timeline/dm_timeline.js b/src/components/dm_timeline/dm_timeline.js deleted file mode 100644 index d54044ff7..000000000 --- a/src/components/dm_timeline/dm_timeline.js +++ /dev/null @@ -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 diff --git a/src/components/dm_timeline/dm_timeline.vue b/src/components/dm_timeline/dm_timeline.vue deleted file mode 100644 index c4e4d0703..000000000 --- a/src/components/dm_timeline/dm_timeline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/components/draft/draft.js b/src/components/draft/draft.js index 49e186eae..1c7f419c6 100644 --- a/src/components/draft/draft.js +++ b/src/components/draft/draft.js @@ -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() { diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index b3b538a9a..f8b121232 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -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) { diff --git a/src/components/friends_timeline/friends_timeline.js b/src/components/friends_timeline/friends_timeline.js deleted file mode 100644 index b6bee7305..000000000 --- a/src/components/friends_timeline/friends_timeline.js +++ /dev/null @@ -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 diff --git a/src/components/friends_timeline/friends_timeline.vue b/src/components/friends_timeline/friends_timeline.vue deleted file mode 100644 index 01a568123..000000000 --- a/src/components/friends_timeline/friends_timeline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js index a06220a37..ae58e58ed 100644 --- a/src/components/lists_timeline/lists_timeline.js +++ b/src/components/lists_timeline/lists_timeline.js @@ -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' }) }, } diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.js b/src/components/public_and_external_timeline/public_and_external_timeline.js deleted file mode 100644 index 6dc8df02a..000000000 --- a/src/components/public_and_external_timeline/public_and_external_timeline.js +++ /dev/null @@ -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 diff --git a/src/components/public_and_external_timeline/public_and_external_timeline.vue b/src/components/public_and_external_timeline/public_and_external_timeline.vue deleted file mode 100644 index fcd915acb..000000000 --- a/src/components/public_and_external_timeline/public_and_external_timeline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/components/public_timeline/public_timeline.js b/src/components/public_timeline/public_timeline.js deleted file mode 100644 index bbeae47af..000000000 --- a/src/components/public_timeline/public_timeline.js +++ /dev/null @@ -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 diff --git a/src/components/public_timeline/public_timeline.vue b/src/components/public_timeline/public_timeline.vue deleted file mode 100644 index 5720068df..000000000 --- a/src/components/public_timeline/public_timeline.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js index 533d19313..089cb1d26 100644 --- a/src/components/quick_filter_settings/quick_filter_settings.js +++ b/src/components/quick_filter_settings/quick_filter_settings.js @@ -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) diff --git a/src/components/quote/quote.js b/src/components/quote/quote.js index 439d3440a..1f230b488 100644 --- a/src/components/quote/quote.js +++ b/src/components/quote/quote.js @@ -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 }) diff --git a/src/components/quotes_timeline/quotes_timeline.js b/src/components/quotes_timeline/quotes_timeline.js index f92f109be..4778f1daa 100644 --- a/src/components/quotes_timeline/quotes_timeline.js +++ b/src/components/quotes_timeline/quotes_timeline.js @@ -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, diff --git a/src/components/search/search.js b/src/components/search/search.js index 2a6ed5c3e..e4c2c5674 100644 --- a/src/components/search/search.js +++ b/src/components/search/search.js @@ -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, ) }, }, diff --git a/src/components/settings_modal/tabs/clutter_tab.js b/src/components/settings_modal/tabs/clutter_tab.js index 87269eb99..a3f6e77a5 100644 --- a/src/components/settings_modal/tabs/clutter_tab.js +++ b/src/components/settings_modal/tabs/clutter_tab.js @@ -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() }, }, } diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index 3a9cad6d2..9d49eca81 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -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( diff --git a/src/components/status/status.js b/src/components/status/status.js index 5839eadde..1f942827b 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -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 diff --git a/src/components/status_action_buttons/buttons_definitions.js b/src/components/status_action_buttons/buttons_definitions.js index 6c94b43a5..b13bed033 100644 --- a/src/components/status_action_buttons/buttons_definitions.js +++ b/src/components/status_action_buttons/buttons_definitions.js @@ -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, diff --git a/src/components/status_history_modal/status_history_modal.js b/src/components/status_history_modal/status_history_modal.js index c165fe411..b27c19b73 100644 --- a/src/components/status_history_modal/status_history_modal.js +++ b/src/components/status_history_modal/status_history_modal.js @@ -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() diff --git a/src/components/status_popover/status_popover.js b/src/components/status_popover/status_popover.js index 95da91ba7..e9fc2398d 100644 --- a/src/components/status_popover/status_popover.js +++ b/src/components/status_popover/status_popover.js @@ -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)) } diff --git a/src/components/tag_timeline/tag_timeline.js b/src/components/tag_timeline/tag_timeline.js index e82e86cfd..da3fcb693 100644 --- a/src/components/tag_timeline/tag_timeline.js +++ b/src/components/tag_timeline/tag_timeline.js @@ -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, diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index de025a253..e3ba35e07 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -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, }), diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 287934657..38a2ae144 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -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 /> diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js index 310d66d1d..49065c2a9 100644 --- a/src/components/user_profile/user_profile.js +++ b/src/components/user_profile/user_profile.js @@ -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 }) } diff --git a/src/modules/api.js b/src/modules/api.js index d91fc04fb..e686df0fe 100644 --- a/src/modules/api.js +++ b/src/modules/api.js @@ -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: diff --git a/src/modules/index.js b/src/modules/index.js index 8b48e4e31..6aa236257 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -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, diff --git a/src/modules/statuses.js b/src/modules/statuses.js deleted file mode 100644 index 7a1444ad3..000000000 --- a/src/modules/statuses.js +++ /dev/null @@ -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 diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index af000a40e..a25f60107 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -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, diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js index 647ed90ef..b7097f4e0 100644 --- a/src/services/timeline_fetcher/timeline_fetcher.service.js +++ b/src/services/timeline_fetcher/timeline_fetcher.service.js @@ -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, diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 766da26fb..c00b7a29b 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -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 diff --git a/src/stores/notifications.js b/src/stores/notifications.js index 507e40759..1099b0e83 100644 --- a/src/stores/notifications.js +++ b/src/stores/notifications.js @@ -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 diff --git a/src/stores/reports.js b/src/stores/reports.js index 7d319d8e3..1c4d89bca 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -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( diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 1fda114cd..55060e251 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -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 }, }, }) diff --git a/src/stores/users.js b/src/stores/users.js index 7554d937e..4aba00743 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -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: '/' }) diff --git a/test/unit/specs/components/post_status_form.spec.js b/test/unit/specs/components/post_status_form.spec.js index 50b7471f2..0c0be4143 100644 --- a/test/unit/specs/components/post_status_form.spec.js +++ b/test/unit/specs/components/post_status_form.spec.js @@ -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, } },