diff --git a/changelog.d/avatar_mentions.change b/changelog.d/avatar_mentions.change deleted file mode 100644 index f75f02ea4..000000000 --- a/changelog.d/avatar_mentions.change +++ /dev/null @@ -1 +0,0 @@ -If user avatars next to mentions are enabled it will show empty placeholder avatar next to label while user is being fetched, to avoid jumps diff --git a/changelog.d/follower-remove.fix b/changelog.d/follower-remove.fix deleted file mode 100644 index 4b78b087d..000000000 --- a/changelog.d/follower-remove.fix +++ /dev/null @@ -1 +0,0 @@ -Fix follower remove API call diff --git a/changelog.d/streaming_indicator.add b/changelog.d/streaming_indicator.add index f2abe04a1..547307384 100644 --- a/changelog.d/streaming_indicator.add +++ b/changelog.d/streaming_indicator.add @@ -1,3 +1 @@ -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 +Added an indicator next to instance's name showing WebSocket connection status (if enabled) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 69fca2f2c..cc4aecfe3 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -1,5 +1,6 @@ import { get, reduce } from 'lodash' -import { mapState } from 'pinia' +import { mapState as mapPiniaState } from 'pinia' +import { mapState } from 'vuex' import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue' import PostStatusForm from 'src/components/post_status_form/post_status_form.vue' @@ -12,7 +13,6 @@ 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 { useStreamingStore } from 'src/stores/streaming.js' import { fetchConversation, fetchStatus } from 'src/api/public.js' import { WSConnectionStatus } from 'src/api/websocket.js' @@ -93,7 +93,6 @@ const conversation = { default: false, }, }, - emits: ['update:virtualHeight'], data() { return { focused: null, @@ -102,7 +101,6 @@ const conversation = { inlineDivePosition: null, loadStatusError: null, unsuspendibleIds: new Set(), - virtualHeight: 120, } }, created() { @@ -110,9 +108,6 @@ const conversation = { this.fetchConversation() } }, - mounted() { - this.updateVirtualHeight() - }, computed: { status() { return useStatusesStore().allStatuses.get(this.statusId) @@ -365,8 +360,8 @@ const conversation = { return !!(this.expanded || this.isPage) }, hiddenStyle() { - if (this.isExpanded) return {} - return { height: this.virtualHeight + 'px' } + const height = this.status?.virtualHeight || '120px' + return this.virtualHidden ? { height } : {} }, threadDisplayStatus() { return this.conversation.reduce((a, k) => { @@ -393,11 +388,11 @@ const conversation = { maybeFocused() { return this.isExpanded ? this.focused : null }, - ...mapState(useMergedConfigStore, ['mergedConfig']), - ...mapState(useStreamingStore, { - mastoUserSocketStatus: (state) => state.state, + ...mapPiniaState(useMergedConfigStore, ['mergedConfig']), + ...mapState({ + mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus, }), - ...mapState(useInterfaceStore, { + ...mapPiniaState(useInterfaceStore, { mobileLayout: (store) => store.layoutType === 'mobile', }), }, @@ -431,7 +426,10 @@ const conversation = { } }, virtualHidden() { - this.updateVirtualHeight() + useStatusesStore().setVirtualHeight({ + statusId: this.statusId, + height: `${this.$el.clientHeight}px`, + }) }, }, methods: { @@ -620,16 +618,6 @@ const conversation = { this.$router.push({ name: 'conversation', params: { id: data.id } }) } }, - updateVirtualHeight() { - this.$nextTick(() => { - this.virtualHeight = this.$refs.body.getBoundingClientRect().height - this.$emit('update:virtualHeight', { - id: this.status.id, - height: this.virtualHeight, - top: this.$el.clientTop, - }) - }) - }, }, } diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index cc1376978..ae6e3a0ce 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -40,7 +40,6 @@

@@ -57,7 +56,6 @@

diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js index 15a5cfb15..32d0b37a9 100644 --- a/src/components/desktop_nav/desktop_nav.js +++ b/src/components/desktop_nav/desktop_nav.js @@ -135,9 +135,9 @@ export default { this.showConfirmLogout() } }, - async doLogout() { - await useUsersStore().logout() + doLogout() { this.$router.replace('/main/public') + useUsersStore().logout() this.hideConfirmLogout() }, onSearchBarToggled(hidden) { diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js index 8801d2c2e..6c2750677 100644 --- a/src/components/login_form/login_form.js +++ b/src/components/login_form/login_form.js @@ -1,9 +1,9 @@ -import { mapActions, mapState } from 'pinia' +import { mapActions, mapState as mapPiniaState } from 'pinia' +import { mapState } from 'vuex' 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,10 +18,12 @@ const LoginForm = { error: false, }), computed: { - ...mapState(useUsersStore, ['loggingIn']), - ...mapState(useOAuthStore, ['clientId', 'clientSecret']), - ...mapState(useInstanceStore, ['server', 'registrationOpen']), - ...mapState(useAuthFlowStore, { + ...mapState({ + loggingIn: (state) => state.users.loggingIn, + }), + ...mapPiniaState(useOAuthStore, ['clientId', 'clientSecret']), + ...mapPiniaState(useInstanceStore, ['server', 'registrationOpen']), + ...mapPiniaState(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 5db90ab41..374e62259 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -29,7 +29,7 @@ const MentionLink = { }, props: { url: { - required: false, + required: true, type: String, }, content: { @@ -75,11 +75,11 @@ const MentionLink = { }, computed: { user() { - return this.url ? useUsersStore().findUserByUrl(this.url) : null + return this.url && useUsersStore().findUserByUrl(this.url) }, isYou() { - if (!this.currentUser) return false - return this.user === this.currentUser + // FIXME why user !== currentUser??? + return this.user?.id === this.currentUser.id }, userName() { return this.user && this.userNameFullUi.split('@')[0] diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index 3973522b0..33f0d9db7 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -8,20 +8,15 @@ :href="url" class="original" target="_blank" - > - - - + v-html="content" + /> store.supportsAnnouncements, }), - ...mapState(useInstanceCapabilitiesStore, [ + ...mapPiniaState(useInstanceCapabilitiesStore, [ 'pleromaChatMessagesAvailable', 'pleromaBookmarkFoldersAvailable', 'localBubble', ]), - ...mapState(useInstanceStore, ['federating']), - ...mapState(useInstanceStore, { + ...mapPiniaState(useInstanceStore, ['federating']), + ...mapPiniaState(useInstanceStore, { privateMode: (store) => store.private, }), - ...mapState(useSyncConfigStore, { + ...mapPiniaState(useSyncConfigStore, { collapsed: (store) => store.prefsStorage.simple.collapseNav, pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), - ...mapState(useUsersStore, ['currentUser']), - ...mapVuexState({ + ...mapPiniaState(useUsersStore, ['currentUser']), + ...mapState({ followRequestCount: (state) => state.api.followRequests.length, }), - ...mapState(useChatsStore, ['unreadChatsCount']), + ...mapPiniaState(useChatsStore, ['unreadChatsCount']), timelinesItems() { return filterNavigation( Object.entries({ ...TIMELINES }) diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js index 2efa94ee4..d1be73819 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -1,5 +1,5 @@ -import { mapState } from 'pinia' -import { mapState as mapVuexState } from 'vuex' +import { mapState as mapPiniaState } from 'pinia' +import { mapState } from 'vuex' import { filterNavigation, @@ -59,26 +59,26 @@ const NavPanel = { getters() { return this.$store.getters }, - ...mapState(useListsStore, { + ...mapPiniaState(useListsStore, { lists: getListEntries, }), - ...mapState(useAnnouncementsStore, { + ...mapPiniaState(useAnnouncementsStore, { supportsAnnouncements: (store) => store.supportsAnnouncements, }), - ...mapState(useBookmarkFoldersStore, { + ...mapPiniaState(useBookmarkFoldersStore, { bookmarks: getBookmarkFolderEntries, }), - ...mapState(useSyncConfigStore, { + ...mapPiniaState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), - ...mapState(useInstanceStore, ['privateMode', 'federating']), - ...mapState(useInstanceCapabilitiesStore, [ + ...mapPiniaState(useInstanceStore, ['privateMode', 'federating']), + ...mapPiniaState(useInstanceCapabilitiesStore, [ 'pleromaChatMessagesAvailable', 'localBubble', ]), - ...mapState(useUsersStore, ['currentUser']), - ...mapVuexState({ + ...mapPiniaState(useUsersStore, ['currentUser']), + ...mapState({ followRequestCount: (state) => state.api.followRequests.length, }), pinnedList() { diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js index 3c82f455c..a589c0cf6 100644 --- a/src/components/notification/notification.js +++ b/src/components/notification/notification.js @@ -118,6 +118,9 @@ const Notification = { useInstanceStore().restrictedNicknames, ) }, + getUser(notification) { + return this.$store.state.users.usersObject[notification.from_profile.id] + }, interacted() { this.$emit('interacted') }, diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js index 824ab7bda..bf6e03c9e 100644 --- a/src/components/oauth_callback/oauth_callback.js +++ b/src/components/oauth_callback/oauth_callback.js @@ -16,10 +16,10 @@ const oac = { clientSecret, instance: useInstanceStore().server, code: this.code, - }).then(async ({ data: result }) => { + }).then(({ data: result }) => { oauthStore.setToken(result.access_token) - await useUsersStore().loginUser(result.access_token) + useUsersStore().loginUser(result.access_token) this.$router.push({ name: 'friends' }) }) } diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js index 482524eb6..089cb1d26 100644 --- a/src/components/quick_filter_settings/quick_filter_settings.js +++ b/src/components/quick_filter_settings/quick_filter_settings.js @@ -28,7 +28,7 @@ const QuickFilterSettings = { path: 'replyVisibility', value: visibility, }) - useStatusesStore().requireReloadAll() + useStatusesStore().queueFlushAll() }, openTab(tab) { useInterfaceStore().openSettingsModalTab(tab) diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue index 0ad7c026a..0abcfe656 100644 --- a/src/components/registration/registration.vue +++ b/src/components/registration/registration.vue @@ -303,7 +303,7 @@
diff --git a/src/components/settings_modal/helpers/vertical_tab_switcher.jsx b/src/components/settings_modal/helpers/vertical_tab_switcher.jsx index 2f3d5e28f..996939c00 100644 --- a/src/components/settings_modal/helpers/vertical_tab_switcher.jsx +++ b/src/components/settings_modal/helpers/vertical_tab_switcher.jsx @@ -1,6 +1,6 @@ // eslint-disable-next-line no-unused -import { mapState } from 'pinia' +import { mapState as mapPiniaState } from 'pinia' import { Fragment } from 'vue' import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome' @@ -60,7 +60,7 @@ export default { return this.$slots.default().findIndex(isWanted) === this.activeIndex } }, - ...mapState(useInterfaceStore, { + ...mapPiniaState(useInterfaceStore, { mobileLayout: (store) => store.layoutType === 'mobile', }), }, diff --git a/src/components/settings_modal/tabs/clutter_tab.js b/src/components/settings_modal/tabs/clutter_tab.js index 18300231c..a3f6e77a5 100644 --- a/src/components/settings_modal/tabs/clutter_tab.js +++ b/src/components/settings_modal/tabs/clutter_tab.js @@ -36,7 +36,7 @@ const ClutterTab = { // Updating nested properties watch: { replyVisibility() { - useStatusesStore().requireReloadAll() + useStatusesStore().queueFlushAll() }, }, } diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index fbc7b8902..9d49eca81 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -266,7 +266,7 @@ const FilteringTab = { // Updating nested properties watch: { replyVisibility() { - useStatusesStore().requireReloadAll() + useStatusesStore().queueFlushAll() }, muteFiltersObject() { this.muteFiltersDraftObject = cloneDeep( diff --git a/src/components/status/status.js b/src/components/status/status.js index 16625235d..749e0a12e 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -105,6 +105,7 @@ const Status = { isPreview: Boolean, noHeading: Boolean, inlineExpanded: Boolean, + showPinned: Boolean, inProfile: Boolean, inConversation: Boolean, inQuote: Boolean, @@ -117,13 +118,7 @@ const Status = { threadDisplayStatus: String, }, - emits: [ - 'goto', - 'dive', - 'toggleExpanded', - 'suspendableStateChange', - 'heightChange', - ], + emits: ['goto', 'dive', 'toggleExpanded', 'suspendableStateChange'], data() { return { replying: false, @@ -194,7 +189,7 @@ const Status = { ) // User referenced in post might not be yet present in store - // since their data is not included in status data, just the id + // since their data is not included in status data return user?.statusnet_profile_url } }, @@ -375,7 +370,7 @@ const Status = { }, replyToName() { if (this.mainStatus.in_reply_to_screen_name) { - return this.mainStatus.in_reply_to_screen_name + return this.status.in_reply_to_screen_name } else { const user = useUsersStore().findUser( this.mainStatus.in_reply_to_user_id, @@ -392,7 +387,7 @@ const Status = { return uniqBy(combinedUsers, 'id') }, tags() { - return [...this.status.tags] + return this.status.tags .filter((tagObj) => Object.hasOwn(tagObj, 'name')) .map((tagObj) => tagObj.name) .join(' ') @@ -544,7 +539,6 @@ const Status = { this.headTailLinks = headTailLinks }, toggleThreadDisplay() { - // FIXME this.controlledToggleThreadDisplay() }, scrollIfFocused(focused) { @@ -563,22 +557,8 @@ const Status = { } } }, - onTransitionEnd() { - this.$nextTick(() => { - this.$emit('heightChange') - }) - }, }, watch: { - status: { - deep: true, - handler() { - this.$emit('heightChange') - }, - }, - replying() { - this.$emit('heightChange') - }, focused: function (id) { this.scrollIfFocused(id) }, @@ -604,7 +584,6 @@ const Status = { }, isSuspendable: function (suspend) { this.$emit('suspendableStateChange', { id: this.status.id, suspend }) - this.$emit('heightChange') }, }, } diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 0db67f24b..f0b2f1277 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -169,7 +169,7 @@
- +
- + - diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx index b21d4eb04..01913f709 100644 --- a/src/components/tab_switcher/tab_switcher.jsx +++ b/src/components/tab_switcher/tab_switcher.jsx @@ -101,10 +101,6 @@ export default { classesTab.push('active') classesWrapper.push('active') } - if (props.disabled) { - classesTab.push('disabled') - classesWrapper.push('disabled') - } if (props.image) { return (
diff --git a/src/components/thread_tree/thread_tree.js b/src/components/thread_tree/thread_tree.js index 6bcfa469f..491fab78f 100644 --- a/src/components/thread_tree/thread_tree.js +++ b/src/components/thread_tree/thread_tree.js @@ -30,7 +30,7 @@ const ThreadTree = { totalReplyCount: Object, totalReplyDepth: Object, }, - emits: ['suspendableStateChange', 'goto', 'dive', 'heightChange'], + emits: ['suspendableStateChange', 'goto', 'dive'], computed: { currentReplies() { return this.getReplies(this.statusId).map(({ id }) => id) diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue index be95f8698..2dae7beee 100644 --- a/src/components/thread_tree/thread_tree.vue +++ b/src/components/thread_tree/thread_tree.vue @@ -22,7 +22,6 @@ @goto="$emit('goto', statusId)" @toggle-expanded="toggleExpanded" @suspendable-state-change="e => $emit('suspendableStateChange', e)" - @height-change="e => $emit('heightChange', e)" />
useStatusesStore().allStatuses.get(id)) .filter(({ pinned }) => (this.skipPinned ? !pinned : true)) }, - count() { - return this.timeline.order.length - }, newStatusCount() { return this.timeline.newStatusCount }, showLoadButton() { - return this.timeline.newStatusCount > 0 || this.timeline.reloadNeeded + return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0 }, loadButtonString() { - if (this.timeline.reloadNeeded) { + if (this.timeline.flushMarker !== 0) { return this.$t('timeline.reload') } else { return `${this.$t('timeline.show_new')} (${this.newStatusCount})` } }, mobileLoadButtonString() { - if (this.timeline.reloadNeeded) { + if (this.timeline.flushMarker !== 0) { return '+' } else { return this.newStatusCount > 99 ? '∞' : this.newStatusCount @@ -101,7 +99,6 @@ const Timeline = { } }, statusesToDisplay() { - if (!this.virtualScrollingEnabled) return this.visibleStatusIds const amount = this.timeline.visibleStatusIds.size const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80)) const min = Math.max(0, this.virtualScrollIndex - statusesPerSide) @@ -170,8 +167,9 @@ const Timeline = { if (e.key === '.') this.showNewStatuses() }, showNewStatuses() { - if (this.timeline.reloadNeeded) { + if (this.timeline.flushMarker !== 0) { useTimelinesStore().clearTimeline(this.timelineRef.name) + useTimelinesStore().queueFlush(this.timelineRef.name, '') this.fetchOlderStatuses() } else { this.blockClicksTemporarily() @@ -192,12 +190,13 @@ const Timeline = { if (!this.virtualScrollingEnabled) return const statuses = this.$refs.timeline.children - if (statuses.length === 0) return const cappedScrollIndex = Math.max( 0, Math.min(this.virtualScrollIndex, statuses.length - 1), ) + if (statuses.length === 0) return + const height = Math.max(document.body.offsetHeight, window.pageYOffset) const centerOfScreen = window.pageYOffset + window.innerHeight * 0.5 @@ -235,11 +234,11 @@ const Timeline = { this.virtualScrollIndex = approxIndex }, scrollLoad() { - // TODO simplify this logic const bodyBRect = document.body.getBoundingClientRect() const height = Math.max(bodyBRect.height, -bodyBRect.y) if ( - !this.timeline.fetcher.loadingOlder.value && + !this.timeline.fetcher.loading.value && + this.$el.offsetHeight > 0 && window.innerHeight + window.pageYOffset >= height - 750 ) { this.fetchOlderStatuses() diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue index 1435bba48..745d8c399 100644 --- a/src/components/timeline/timeline.vue +++ b/src/components/timeline/timeline.vue @@ -1,6 +1,5 @@