diff --git a/changelog.d/streaming.add b/changelog.d/streaming.add new file mode 100644 index 000000000..c6e7de171 --- /dev/null +++ b/changelog.d/streaming.add @@ -0,0 +1 @@ +More timelines now can utilize WebSocket streaming diff --git a/changelog.d/streaming.fix b/changelog.d/streaming.fix new file mode 100644 index 000000000..660a6694f --- /dev/null +++ b/changelog.d/streaming.fix @@ -0,0 +1 @@ +switches between streaming and polling should be more reliable now diff --git a/changelog.d/streaming_indicator.add b/changelog.d/streaming_indicator.add new file mode 100644 index 000000000..f2abe04a1 --- /dev/null +++ b/changelog.d/streaming_indicator.add @@ -0,0 +1,3 @@ +Added an indicator next to instance's name showing WebSocket connection status (if enabled). +Timeline no longer show "loading" indicator at the bottom when fetching newer posts. +Added small indicator on top of timeline when new posts are being fetched diff --git a/src/App.js b/src/App.js index d7a63bd8a..ed3d43e4a 100644 --- a/src/App.js +++ b/src/App.js @@ -21,6 +21,7 @@ import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.j import { useInterfaceStore } from 'src/stores/interface.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useShoutStore } from 'src/stores/shout.js' +import { useUsersStore } from 'src/stores/users.js' // Helper to unwrap reactive proxies window.toValue = (x) => JSON.parse(JSON.stringify(x)) @@ -153,11 +154,8 @@ export default { ...(navbarColumnStretch ? ['-column-stretch'] : []), ] }, - currentUser() { - return this.$store.state.users.currentUser - }, userBackground() { - return this.currentUser.background_image + return this.currentUser?.background_image }, foreignProfileBackground() { return ( @@ -246,6 +244,7 @@ export default { 'styleDataUsed', 'layoutType', ]), + ...mapState(useUsersStore, ['currentUser']), ...mapState(useInstanceStore, ['styleDataUsed']), ...mapState(useInstanceCapabilitiesStore, [ 'suggestionsEnabled', diff --git a/src/App.vue b/src/App.vue index 84a6f7d3d..bd19c5c10 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,7 +26,7 @@ class="column -scrollable" :class="{ '-show-scrollbar': showScrollbars }" > - + diff --git a/src/components/follow_request_card/follow_request_card.js b/src/components/follow_request_card/follow_request_card.js index 294dd2472..c4b43811a 100644 --- a/src/components/follow_request_card/follow_request_card.js +++ b/src/components/follow_request_card/follow_request_card.js @@ -1,9 +1,9 @@ import { defineAsyncComponent } from 'vue' -import { notificationsFromStore } from '../../services/notification_utils/notification_utils.js' import BasicUserCard from '../basic_user_card/basic_user_card.vue' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useNotificationsStore } from 'src/stores/notifications.js' import { useOAuthStore } from 'src/stores/oauth.js' import { approveUser, denyUser } from 'src/api/user.js' @@ -24,7 +24,7 @@ const FollowRequestCard = { }, methods: { findFollowRequestNotificationId() { - const notif = notificationsFromStore(this.$store).find( + const notif = useNotificationsStore().data.find( (notif) => notif.from_profile.id === this.user.id && notif.type === 'follow_request', @@ -55,16 +55,11 @@ const FollowRequestCard = { id: this.user.id, credentials: useOAuthStore().token, }) + // TODO fix this.$store.dispatch('removeFollowRequest', this.user) const notifId = this.findFollowRequestNotificationId() - this.$store.dispatch('markSingleNotificationAsSeen', { id: notifId }) - this.$store.dispatch('updateNotification', { - id: notifId, - updater: (notification) => { - notification.type = 'follow' - }, - }) + useNotificationsStore().markSingleNotificationAsSeen(notifId) this.hideApproveConfirmDialog() }, denyUser() { @@ -81,7 +76,8 @@ const FollowRequestCard = { id: this.user.id, credentials: useOAuthStore().token, }).then(() => { - this.$store.dispatch('dismissNotificationLocal', { id: notifId }) + useNotificationsStore().dismissNotificationLocal(notifId) + // TODO fix this.$store.dispatch('removeFollowRequest', this.user) }) this.hideDenyConfirmDialog() 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/interactions/interactions.js b/src/components/interactions/interactions.js index d008c4b30..37873605f 100644 --- a/src/components/interactions/interactions.js +++ b/src/components/interactions/interactions.js @@ -1,6 +1,8 @@ import Notifications from 'src/components/notifications/notifications.vue' import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx' +import { useUsersStore } from 'src/stores/users.js' + const tabModeDict = { mentions: ['mention'], statuses: ['status'], @@ -14,10 +16,9 @@ const tabModeDict = { const Interactions = { data() { return { - allowFollowingMove: - this.$store.state.users.currentUser.allow_following_move, + allowFollowingMove: useUsersStore().currentUser.allow_following_move, filterMode: tabModeDict.mentions, - canSeeReports: this.$store.state.users.currentUser.privileges.has( + canSeeReports: useUsersStore().currentUser.privileges.has( 'reports_manage_reports', ), } diff --git a/src/components/list/list.js b/src/components/list/list.js index c7b924258..8abf02fd6 100644 --- a/src/components/list/list.js +++ b/src/components/list/list.js @@ -21,8 +21,8 @@ const List = { default: () => '', }, preSelect: { - type: Array, - default: [], + type: Set, + default: new Set(), }, nonInteractive: { type: Boolean, @@ -48,7 +48,7 @@ const List = { data() { return { items: [], - selected: new Set(this.preSelect), + selected: new Set(this.preSelect), // clone loading: false, bottomedOut: true, error: null, @@ -99,11 +99,11 @@ const List = { this.fetchFunction(this.page) .then((result) => { this.loading = false - this.bottomedOut = isEmpty(result.items) + this.bottomedOut = isEmpty(result) if (this.externalItems) return this.page += 1 - this.total = result.count - this.items.push(...result.items) + this.total = result.length + this.items.push(...result) }) .catch((error) => { this.loading = false diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js index 7eb035091..cea9236a5 100644 --- a/src/components/lists_edit/lists_edit.js +++ b/src/components/lists_edit/lists_edit.js @@ -1,5 +1,4 @@ -import { mapState as mapPiniaState } from 'pinia' -import { mapGetters, mapState } from 'vuex' +import { mapState } from 'pinia' import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue' import ListsUserSearch from 'src/components/lists_user_search/lists_user_search.vue' @@ -9,6 +8,7 @@ import UserAvatar from 'src/components/user_avatar/user_avatar.vue' import { useInterfaceStore } from 'src/stores/interface.js' import { useListsStore } from 'src/stores/lists.js' +import { useUsersStore } from 'src/stores/users.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' @@ -47,8 +47,8 @@ const ListsNew = { .fetchListAccounts({ listId: this.id }) .then(() => { this.membersUserIds = this.findListAccounts(this.id) - this.membersUserIds.forEach((userId) => { - this.$store.dispatch('fetchUserIfMissing', userId) + this.membersUserIds.forEach((id) => { + useUsersStore().fetchUserIfMissing({ id }) }) }) }, @@ -66,11 +66,8 @@ const ListsNew = { .map((userId) => this.findUser(userId)) .filter(Boolean) }, - ...mapState({ - currentUser: (state) => state.users.currentUser, - }), - ...mapPiniaState(useListsStore, ['findListTitle', 'findListAccounts']), - ...mapGetters(['findUser']), + ...mapState(useUsersStore, ['currentUser', 'findUser']), + ...mapState(useListsStore, ['findListTitle', 'findListAccounts']), }, methods: { onInput() { diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js index 337ee4d4f..7c422d033 100644 --- a/src/components/lists_menu/lists_menu_content.js +++ b/src/components/lists_menu/lists_menu_content.js @@ -1,10 +1,10 @@ -import { mapState as mapPiniaState } from 'pinia' -import { mapState } from 'vuex' +import { mapState } from 'pinia' import { getListEntries } from 'src/components/navigation/filter.js' import NavigationEntry from 'src/components/navigation/navigation_entry.vue' import { useListsStore } from 'src/stores/lists.js' +import { useUsersStore } from 'src/stores/users.js' export const ListsMenuContent = { props: ['showPin'], @@ -12,12 +12,10 @@ export const ListsMenuContent = { NavigationEntry, }, computed: { - ...mapPiniaState(useListsStore, { + ...mapState(useListsStore, { lists: getListEntries, }), - ...mapState({ - currentUser: (state) => state.users.currentUser, - }), + ...mapState(useUsersStore, ['currentUser']), }, } diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js deleted file mode 100644 index a06220a37..000000000 --- a/src/components/lists_timeline/lists_timeline.js +++ /dev/null @@ -1,47 +0,0 @@ -import Timeline from 'src/components/timeline/timeline.vue' - -import { useListsStore } from 'src/stores/lists.js' - -const ListsTimeline = { - data() { - return { - listId: null, - } - }, - components: { - Timeline, - }, - computed: { - timeline() { - return this.$store.state.statuses.timelines.list - }, - }, - watch: { - $route: function (route) { - 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' }) - useListsStore().fetchList({ listId: this.listId }) - this.$store.dispatch('startFetchingTimeline', { - timeline: 'list', - listId: this.listId, - }) - } - }, - }, - created() { - this.listId = this.$route.params.id - useListsStore().fetchList({ listId: this.listId }) - this.$store.dispatch('startFetchingTimeline', { - timeline: 'list', - listId: this.listId, - }) - }, - unmounted() { - this.$store.dispatch('stopFetchingTimeline', 'list') - this.$store.commit('clearTimeline', { timeline: 'list' }) - }, -} - -export default ListsTimeline diff --git a/src/components/lists_timeline/lists_timeline.vue b/src/components/lists_timeline/lists_timeline.vue deleted file mode 100644 index 18156b812..000000000 --- a/src/components/lists_timeline/lists_timeline.vue +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js index aed3f1ce7..22568135a 100644 --- a/src/components/lists_user_search/lists_user_search.js +++ b/src/components/lists_user_search/lists_user_search.js @@ -2,6 +2,8 @@ import { debounce } from 'lodash' import Checkbox from 'src/components/checkbox/checkbox.vue' +import { useSearchStore } from 'src/stores/search.js' + import { library } from '@fortawesome/fontawesome-svg-core' import { faChevronLeft, faSearch } from '@fortawesome/free-solid-svg-icons' @@ -32,8 +34,8 @@ const ListsUserSearch = { this.loading = true this.$emit('loading') this.userIds = [] - this.$store - .dispatch('search', { + useSearchStore() + .search({ q: query, resolve: true, type: 'accounts', diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 6c2750677..8801d2c2e 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,9 +1,9 @@ -import { mapActions, mapState as mapPiniaState } from 'pinia' -import { mapState } from 'vuex' +import { mapActions, mapState } from 'pinia' import { useAuthFlowStore } from 'src/stores/auth_flow.js' import { useInstanceStore } from 'src/stores/instance.js' import { useOAuthStore } from 'src/stores/oauth.js' +import { useUsersStore } from 'src/stores/users.js' import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js' @@ -18,12 +18,10 @@ const LoginForm = { error: false, }), computed: { - ...mapState({ - loggingIn: (state) => state.users.loggingIn, - }), - ...mapPiniaState(useOAuthStore, ['clientId', 'clientSecret']), - ...mapPiniaState(useInstanceStore, ['server', 'registrationOpen']), - ...mapPiniaState(useAuthFlowStore, { + ...mapState(useUsersStore, ['loggingIn']), + ...mapState(useOAuthStore, ['clientId', 'clientSecret']), + ...mapState(useInstanceStore, ['server', 'registrationOpen']), + ...mapState(useAuthFlowStore, { isTokenAuth: (store) => store.requiredToken, isPasswordAuth: (store) => !store.requiredToken, }), diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 0309079e8..5db90ab41 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -1,5 +1,4 @@ -import { mapState as mapPiniaState } from 'pinia' -import { mapState } from 'vuex' +import { mapState } from 'pinia' import UnicodeDomainIndicator from 'src/components/unicode_domain_indicator/unicode_domain_indicator.vue' import UserAvatar from 'src/components/user_avatar/user_avatar.vue' @@ -12,6 +11,7 @@ import { import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useUserHighlightStore } from 'src/stores/user_highlight.js' +import { useUsersStore } from 'src/stores/users.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -29,7 +29,7 @@ const MentionLink = { }, props: { url: { - required: true, + required: false, type: String, }, content: { @@ -75,11 +75,11 @@ const MentionLink = { }, computed: { user() { - return this.url && this.$store?.getters.findUserByUrl(this.url) + return this.url ? useUsersStore().findUserByUrl(this.url) : null }, isYou() { - // FIXME why user !== currentUser??? - return this.user?.id === this.currentUser.id + if (!this.currentUser) return false + return this.user === this.currentUser }, userName() { return this.user && this.userNameFullUi.split('@')[0] @@ -156,11 +156,9 @@ const MentionLink = { shouldFadeDomain() { return this.mergedConfig.mentionLinkFadeDomain }, - ...mapPiniaState(useMergedConfigStore, ['mergedConfig']), - ...mapPiniaState(useUserHighlightStore, ['highlight']), - ...mapState({ - currentUser: (state) => state.users.currentUser, - }), + ...mapState(useMergedConfigStore, ['mergedConfig']), + ...mapState(useUserHighlightStore, ['highlight']), + ...mapState(useUsersStore, ['currentUser']), }, } diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index 0452cad58..33f0d9db7 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -31,7 +31,7 @@ @ - - - - diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index 4eb956f64..97645ca73 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -5,13 +5,15 @@ import NavigationPins from 'src/components/navigation/navigation_pins.vue' import GestureService from '../../services/gesture_service/gesture_service' import { countExtraNotifications, - unseenNotificationsFromStore, + unseenNotifications, } from '../../services/notification_utils/notification_utils' import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useChatsStore } from 'src/stores/chats.js' import { useInstanceStore } from 'src/stores/instance.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' +import { useNotificationsStore } from 'src/stores/notifications.js' +import { useUsersStore } from 'src/stores/users.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -53,11 +55,10 @@ const MobileNav = { }, computed: { currentUser() { - return this.$store.state.users.currentUser + return useUsersStore().currentUser }, unseenNotifications() { - return unseenNotificationsFromStore( - this.$store, + return unseenNotifications( useMergedConfigStore().mergedConfig.notificationVisibility, useMergedConfigStore().mergedConfig.ignoreInactionableSeen, ) @@ -144,12 +145,12 @@ const MobileNav = { } }, doLogout() { - this.$router.replace('/main/public') - this.$store.dispatch('logout') + useUsersStore().logout() this.hideConfirmLogout() + this.$router.replace('/main/public') }, markNotificationsAsSeen() { - this.$store.dispatch('markNotificationsAsSeen') + useNotificationsStore().markNotificationsAsSeen() }, onScroll({ target: { scrollTop, clientHeight, scrollHeight } }) { this.notificationsAtTop = scrollTop > 0 diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js index 4969352f6..0ffab6f78 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.js +++ b/src/components/mobile_post_status_button/mobile_post_status_button.js @@ -1,7 +1,9 @@ import { debounce } from 'lodash' +import { mapState } from 'pinia' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { usePostStatusStore } from 'src/stores/post_status.js' +import { useUsersStore } from 'src/stores/users.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faPen } from '@fortawesome/free-solid-svg-icons' @@ -33,9 +35,6 @@ const MobilePostStatusButton = { window.removeEventListener('resize', this.handleOSK) }, computed: { - isLoggedIn() { - return !!this.$store.state.users.currentUser - }, isHidden() { if (HIDDEN_FOR_PAGES.has(this.$route.name)) { return true @@ -51,6 +50,7 @@ const MobilePostStatusButton = { autohideFloatingPostButton() { return !!useMergedConfigStore().mergedConfig.autohideFloatingPostButton }, + ...mapState(useUsersStore, ['loggedIn']), }, watch: { autohideFloatingPostButton: function (isEnabled) { diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue index 32079c298..8b11f12d4 100644 --- a/src/components/mobile_post_status_button/mobile_post_status_button.vue +++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue @@ -1,6 +1,6 @@