Merge branch 'users-statuses-pinia' into shigusegubu-themes3
This commit is contained in:
commit
a6f19bb213
43 changed files with 208 additions and 131 deletions
1
changelog.d/avatar_mentions.change
Normal file
1
changelog.d/avatar_mentions.change
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
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
|
||||||
1
changelog.d/follower-remove.fix
Normal file
1
changelog.d/follower-remove.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Fix follower remove API call
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
Added an indicator next to instance's name showing WebSocket connection status (if enabled)
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { get, reduce } from 'lodash'
|
import { get, reduce } from 'lodash'
|
||||||
import { mapState as mapPiniaState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { mapState } from 'vuex'
|
|
||||||
|
|
||||||
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
|
import ChatMessageList from 'src/components/chat_message_list/chat_message_list.vue'
|
||||||
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
import PostStatusForm from 'src/components/post_status_form/post_status_form.vue'
|
||||||
|
|
@ -13,6 +12,7 @@ import { useInterfaceStore } from 'src/stores/interface.js'
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
import { useStatusesStore } from 'src/stores/statuses.js'
|
import { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
|
import { useStreamingStore } from 'src/stores/streaming.js'
|
||||||
|
|
||||||
import { fetchConversation, fetchStatus } from 'src/api/public.js'
|
import { fetchConversation, fetchStatus } from 'src/api/public.js'
|
||||||
import { WSConnectionStatus } from 'src/api/websocket.js'
|
import { WSConnectionStatus } from 'src/api/websocket.js'
|
||||||
|
|
@ -93,6 +93,7 @@ const conversation = {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ['update:virtualHeight'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
focused: null,
|
focused: null,
|
||||||
|
|
@ -101,6 +102,7 @@ const conversation = {
|
||||||
inlineDivePosition: null,
|
inlineDivePosition: null,
|
||||||
loadStatusError: null,
|
loadStatusError: null,
|
||||||
unsuspendibleIds: new Set(),
|
unsuspendibleIds: new Set(),
|
||||||
|
virtualHeight: 120,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
@ -108,6 +110,9 @@ const conversation = {
|
||||||
this.fetchConversation()
|
this.fetchConversation()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateVirtualHeight()
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
status() {
|
status() {
|
||||||
return useStatusesStore().allStatuses.get(this.statusId)
|
return useStatusesStore().allStatuses.get(this.statusId)
|
||||||
|
|
@ -360,8 +365,8 @@ const conversation = {
|
||||||
return !!(this.expanded || this.isPage)
|
return !!(this.expanded || this.isPage)
|
||||||
},
|
},
|
||||||
hiddenStyle() {
|
hiddenStyle() {
|
||||||
const height = this.status?.virtualHeight || '120px'
|
if (this.isExpanded) return {}
|
||||||
return this.virtualHidden ? { height } : {}
|
return { height: this.virtualHeight + 'px' }
|
||||||
},
|
},
|
||||||
threadDisplayStatus() {
|
threadDisplayStatus() {
|
||||||
return this.conversation.reduce((a, k) => {
|
return this.conversation.reduce((a, k) => {
|
||||||
|
|
@ -388,11 +393,11 @@ const conversation = {
|
||||||
maybeFocused() {
|
maybeFocused() {
|
||||||
return this.isExpanded ? this.focused : null
|
return this.isExpanded ? this.focused : null
|
||||||
},
|
},
|
||||||
...mapPiniaState(useMergedConfigStore, ['mergedConfig']),
|
...mapState(useMergedConfigStore, ['mergedConfig']),
|
||||||
...mapState({
|
...mapState(useStreamingStore, {
|
||||||
mastoUserSocketStatus: (state) => state.api.mastoUserSocketStatus,
|
mastoUserSocketStatus: (state) => state.state,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useInterfaceStore, {
|
...mapState(useInterfaceStore, {
|
||||||
mobileLayout: (store) => store.layoutType === 'mobile',
|
mobileLayout: (store) => store.layoutType === 'mobile',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
@ -426,10 +431,7 @@ const conversation = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
virtualHidden() {
|
virtualHidden() {
|
||||||
useStatusesStore().setVirtualHeight({
|
this.updateVirtualHeight()
|
||||||
statusId: this.statusId,
|
|
||||||
height: `${this.$el.clientHeight}px`,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -618,6 +620,16 @@ const conversation = {
|
||||||
this.$router.push({ name: 'conversation', params: { id: data.id } })
|
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,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
<div
|
<div
|
||||||
v-if="isPage && !status"
|
v-if="isPage && !status"
|
||||||
class="conversation-body"
|
class="conversation-body"
|
||||||
|
ref="body"
|
||||||
:class="{ 'panel-body': isExpanded }"
|
:class="{ 'panel-body': isExpanded }"
|
||||||
>
|
>
|
||||||
<p v-if="!loadStatusError">
|
<p v-if="!loadStatusError">
|
||||||
|
|
@ -56,6 +57,7 @@
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="conversation-body"
|
class="conversation-body"
|
||||||
|
ref="body"
|
||||||
:class="{ 'panel-body': isExpanded }"
|
:class="{ 'panel-body': isExpanded }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -116,6 +118,7 @@
|
||||||
@goto="setFocused"
|
@goto="setFocused"
|
||||||
@dive="() => diveIntoStatus(status.id)"
|
@dive="() => diveIntoStatus(status.id)"
|
||||||
@suspendable-state-change="onStatusSuspendStateChange"
|
@suspendable-state-change="onStatusSuspendStateChange"
|
||||||
|
@height-change="updateVirtualHeight"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
|
v-if="showOtherRepliesButtonBelowStatus && getReplies(status.id).length > 1"
|
||||||
|
|
@ -174,6 +177,7 @@
|
||||||
@goto="setFocused"
|
@goto="setFocused"
|
||||||
@dive="diveIntoStatus"
|
@dive="diveIntoStatus"
|
||||||
@suspendable-state-change="onStatusSuspendStateChange"
|
@suspendable-state-change="onStatusSuspendStateChange"
|
||||||
|
@height-change="updateVirtualHeight"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -200,6 +204,7 @@
|
||||||
@goto="setFocused"
|
@goto="setFocused"
|
||||||
@toggle-expanded="toggleExpanded"
|
@toggle-expanded="toggleExpanded"
|
||||||
@suspendable-state-change="onStatusSuspendStateChange"
|
@suspendable-state-change="onStatusSuspendStateChange"
|
||||||
|
@height-change="updateVirtualHeight"
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -135,9 +135,9 @@ export default {
|
||||||
this.showConfirmLogout()
|
this.showConfirmLogout()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doLogout() {
|
async doLogout() {
|
||||||
|
await useUsersStore().logout()
|
||||||
this.$router.replace('/main/public')
|
this.$router.replace('/main/public')
|
||||||
useUsersStore().logout()
|
|
||||||
this.hideConfirmLogout()
|
this.hideConfirmLogout()
|
||||||
},
|
},
|
||||||
onSearchBarToggled(hidden) {
|
onSearchBarToggled(hidden) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { mapActions, mapState as mapPiniaState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import { mapState } from 'vuex'
|
|
||||||
|
|
||||||
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
|
import { getLoginUrl, getTokenWithCredentials } from 'src/api/oauth.js'
|
||||||
|
|
||||||
|
|
@ -18,12 +18,10 @@ const LoginForm = {
|
||||||
error: false,
|
error: false,
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState(useUsersStore, ['loggingIn']),
|
||||||
loggingIn: (state) => state.users.loggingIn,
|
...mapState(useOAuthStore, ['clientId', 'clientSecret']),
|
||||||
}),
|
...mapState(useInstanceStore, ['server', 'registrationOpen']),
|
||||||
...mapPiniaState(useOAuthStore, ['clientId', 'clientSecret']),
|
...mapState(useAuthFlowStore, {
|
||||||
...mapPiniaState(useInstanceStore, ['server', 'registrationOpen']),
|
|
||||||
...mapPiniaState(useAuthFlowStore, {
|
|
||||||
isTokenAuth: (store) => store.requiredToken,
|
isTokenAuth: (store) => store.requiredToken,
|
||||||
isPasswordAuth: (store) => !store.requiredToken,
|
isPasswordAuth: (store) => !store.requiredToken,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const MentionLink = {
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
url: {
|
url: {
|
||||||
required: true,
|
required: false,
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
|
|
@ -75,11 +75,11 @@ const MentionLink = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
user() {
|
user() {
|
||||||
return this.url && useUsersStore().findUserByUrl(this.url)
|
return this.url ? useUsersStore().findUserByUrl(this.url) : null
|
||||||
},
|
},
|
||||||
isYou() {
|
isYou() {
|
||||||
// FIXME why user !== currentUser???
|
if (!this.currentUser) return false
|
||||||
return this.user?.id === this.currentUser.id
|
return this.user === this.currentUser
|
||||||
},
|
},
|
||||||
userName() {
|
userName() {
|
||||||
return this.user && this.userNameFullUi.split('@')[0]
|
return this.user && this.userNameFullUi.split('@')[0]
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,20 @@
|
||||||
:href="url"
|
:href="url"
|
||||||
class="original"
|
class="original"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
v-html="content"
|
><!-- eslint-enable vue/no-v-html -->
|
||||||
/><!-- eslint-enable vue/no-v-html -->
|
<UserAvatar
|
||||||
|
v-if="shouldShowAvatar"
|
||||||
|
class="mention-avatar"
|
||||||
|
:user-id="null"
|
||||||
|
/>
|
||||||
|
<span v-html="content" />
|
||||||
|
</a>
|
||||||
<UserPopover
|
<UserPopover
|
||||||
v-else
|
v-else
|
||||||
:user-id="user.id"
|
:user-id="user.id"
|
||||||
:disabled="!shouldShowTooltip"
|
:disabled="!shouldShowTooltip"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="user"
|
|
||||||
class="new"
|
class="new"
|
||||||
:style="style"
|
:style="style"
|
||||||
:class="classnames"
|
:class="classnames"
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,9 @@ const MobileNav = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doLogout() {
|
doLogout() {
|
||||||
this.$router.replace('/main/public')
|
|
||||||
useUsersStore().logout()
|
useUsersStore().logout()
|
||||||
this.hideConfirmLogout()
|
this.hideConfirmLogout()
|
||||||
|
this.$router.replace('/main/public')
|
||||||
},
|
},
|
||||||
markNotificationsAsSeen() {
|
markNotificationsAsSeen() {
|
||||||
useNotificationsStore().markNotificationsAsSeen()
|
useNotificationsStore().markNotificationsAsSeen()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { mapState as mapPiniaState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { mapState } from 'vuex'
|
import { mapState as mapVuexState } from 'vuex'
|
||||||
|
|
||||||
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
|
import BookmarkFoldersMenuContent from 'src/components/bookmark_folders_menu/bookmark_folders_menu_content.vue'
|
||||||
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
import Checkbox from 'src/components/checkbox/checkbox.vue'
|
||||||
|
|
@ -111,29 +111,29 @@ const NavPanel = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapPiniaState(useAnnouncementsStore, {
|
...mapState(useAnnouncementsStore, {
|
||||||
unreadAnnouncementCount: 'unreadAnnouncementCount',
|
unreadAnnouncementCount: 'unreadAnnouncementCount',
|
||||||
supportsAnnouncements: (store) => store.supportsAnnouncements,
|
supportsAnnouncements: (store) => store.supportsAnnouncements,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useInstanceCapabilitiesStore, [
|
...mapState(useInstanceCapabilitiesStore, [
|
||||||
'pleromaChatMessagesAvailable',
|
'pleromaChatMessagesAvailable',
|
||||||
'pleromaBookmarkFoldersAvailable',
|
'pleromaBookmarkFoldersAvailable',
|
||||||
'localBubble',
|
'localBubble',
|
||||||
]),
|
]),
|
||||||
...mapPiniaState(useInstanceStore, ['federating']),
|
...mapState(useInstanceStore, ['federating']),
|
||||||
...mapPiniaState(useInstanceStore, {
|
...mapState(useInstanceStore, {
|
||||||
privateMode: (store) => store.private,
|
privateMode: (store) => store.private,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useSyncConfigStore, {
|
...mapState(useSyncConfigStore, {
|
||||||
collapsed: (store) => store.prefsStorage.simple.collapseNav,
|
collapsed: (store) => store.prefsStorage.simple.collapseNav,
|
||||||
pinnedItems: (store) =>
|
pinnedItems: (store) =>
|
||||||
new Set(store.prefsStorage.collections.pinnedNavItems),
|
new Set(store.prefsStorage.collections.pinnedNavItems),
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useUsersStore, ['currentUser']),
|
...mapState(useUsersStore, ['currentUser']),
|
||||||
...mapState({
|
...mapVuexState({
|
||||||
followRequestCount: (state) => state.api.followRequests.length,
|
followRequestCount: (state) => state.api.followRequests.length,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useChatsStore, ['unreadChatsCount']),
|
...mapState(useChatsStore, ['unreadChatsCount']),
|
||||||
timelinesItems() {
|
timelinesItems() {
|
||||||
return filterNavigation(
|
return filterNavigation(
|
||||||
Object.entries({ ...TIMELINES })
|
Object.entries({ ...TIMELINES })
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { mapState as mapPiniaState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { mapState } from 'vuex'
|
import { mapState as mapVuexState } from 'vuex'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filterNavigation,
|
filterNavigation,
|
||||||
|
|
@ -59,26 +59,26 @@ const NavPanel = {
|
||||||
getters() {
|
getters() {
|
||||||
return this.$store.getters
|
return this.$store.getters
|
||||||
},
|
},
|
||||||
...mapPiniaState(useListsStore, {
|
...mapState(useListsStore, {
|
||||||
lists: getListEntries,
|
lists: getListEntries,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useAnnouncementsStore, {
|
...mapState(useAnnouncementsStore, {
|
||||||
supportsAnnouncements: (store) => store.supportsAnnouncements,
|
supportsAnnouncements: (store) => store.supportsAnnouncements,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useBookmarkFoldersStore, {
|
...mapState(useBookmarkFoldersStore, {
|
||||||
bookmarks: getBookmarkFolderEntries,
|
bookmarks: getBookmarkFolderEntries,
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useSyncConfigStore, {
|
...mapState(useSyncConfigStore, {
|
||||||
pinnedItems: (store) =>
|
pinnedItems: (store) =>
|
||||||
new Set(store.prefsStorage.collections.pinnedNavItems),
|
new Set(store.prefsStorage.collections.pinnedNavItems),
|
||||||
}),
|
}),
|
||||||
...mapPiniaState(useInstanceStore, ['privateMode', 'federating']),
|
...mapState(useInstanceStore, ['privateMode', 'federating']),
|
||||||
...mapPiniaState(useInstanceCapabilitiesStore, [
|
...mapState(useInstanceCapabilitiesStore, [
|
||||||
'pleromaChatMessagesAvailable',
|
'pleromaChatMessagesAvailable',
|
||||||
'localBubble',
|
'localBubble',
|
||||||
]),
|
]),
|
||||||
...mapPiniaState(useUsersStore, ['currentUser']),
|
...mapState(useUsersStore, ['currentUser']),
|
||||||
...mapState({
|
...mapVuexState({
|
||||||
followRequestCount: (state) => state.api.followRequests.length,
|
followRequestCount: (state) => state.api.followRequests.length,
|
||||||
}),
|
}),
|
||||||
pinnedList() {
|
pinnedList() {
|
||||||
|
|
|
||||||
|
|
@ -118,9 +118,6 @@ const Notification = {
|
||||||
useInstanceStore().restrictedNicknames,
|
useInstanceStore().restrictedNicknames,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
getUser(notification) {
|
|
||||||
return this.$store.state.users.usersObject[notification.from_profile.id]
|
|
||||||
},
|
|
||||||
interacted() {
|
interacted() {
|
||||||
this.$emit('interacted')
|
this.$emit('interacted')
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ const oac = {
|
||||||
clientSecret,
|
clientSecret,
|
||||||
instance: useInstanceStore().server,
|
instance: useInstanceStore().server,
|
||||||
code: this.code,
|
code: this.code,
|
||||||
}).then(({ data: result }) => {
|
}).then(async ({ data: result }) => {
|
||||||
oauthStore.setToken(result.access_token)
|
oauthStore.setToken(result.access_token)
|
||||||
|
|
||||||
useUsersStore().loginUser(result.access_token)
|
await useUsersStore().loginUser(result.access_token)
|
||||||
this.$router.push({ name: 'friends' })
|
this.$router.push({ name: 'friends' })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ const QuickFilterSettings = {
|
||||||
path: 'replyVisibility',
|
path: 'replyVisibility',
|
||||||
value: visibility,
|
value: visibility,
|
||||||
})
|
})
|
||||||
useStatusesStore().queueFlushAll()
|
useStatusesStore().requireReloadAll()
|
||||||
},
|
},
|
||||||
openTab(tab) {
|
openTab(tab) {
|
||||||
useInterfaceStore().openSettingsModalTab(tab)
|
useInterfaceStore().openSettingsModalTab(tab)
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@
|
||||||
<!-- eslint-enable vue/no-v-html -->
|
<!-- eslint-enable vue/no-v-html -->
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="serverValidationErrors.length"
|
v-if="signUpErrors.length"
|
||||||
class="form-group"
|
class="form-group"
|
||||||
>
|
>
|
||||||
<div class="alert error">
|
<div class="alert error">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// eslint-disable-next-line no-unused
|
// eslint-disable-next-line no-unused
|
||||||
|
|
||||||
import { mapState as mapPiniaState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
import { Fragment } from 'vue'
|
import { Fragment } from 'vue'
|
||||||
|
|
||||||
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon as FAIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
@ -60,7 +60,7 @@ export default {
|
||||||
return this.$slots.default().findIndex(isWanted) === this.activeIndex
|
return this.$slots.default().findIndex(isWanted) === this.activeIndex
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapPiniaState(useInterfaceStore, {
|
...mapState(useInterfaceStore, {
|
||||||
mobileLayout: (store) => store.layoutType === 'mobile',
|
mobileLayout: (store) => store.layoutType === 'mobile',
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ const ClutterTab = {
|
||||||
// Updating nested properties
|
// Updating nested properties
|
||||||
watch: {
|
watch: {
|
||||||
replyVisibility() {
|
replyVisibility() {
|
||||||
useStatusesStore().queueFlushAll()
|
useStatusesStore().requireReloadAll()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ const FilteringTab = {
|
||||||
// Updating nested properties
|
// Updating nested properties
|
||||||
watch: {
|
watch: {
|
||||||
replyVisibility() {
|
replyVisibility() {
|
||||||
useStatusesStore().queueFlushAll()
|
useStatusesStore().requireReloadAll()
|
||||||
},
|
},
|
||||||
muteFiltersObject() {
|
muteFiltersObject() {
|
||||||
this.muteFiltersDraftObject = cloneDeep(
|
this.muteFiltersDraftObject = cloneDeep(
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,6 @@ const Status = {
|
||||||
isPreview: Boolean,
|
isPreview: Boolean,
|
||||||
noHeading: Boolean,
|
noHeading: Boolean,
|
||||||
inlineExpanded: Boolean,
|
inlineExpanded: Boolean,
|
||||||
showPinned: Boolean,
|
|
||||||
inProfile: Boolean,
|
inProfile: Boolean,
|
||||||
inConversation: Boolean,
|
inConversation: Boolean,
|
||||||
inQuote: Boolean,
|
inQuote: Boolean,
|
||||||
|
|
@ -118,7 +117,13 @@ const Status = {
|
||||||
|
|
||||||
threadDisplayStatus: String,
|
threadDisplayStatus: String,
|
||||||
},
|
},
|
||||||
emits: ['goto', 'dive', 'toggleExpanded', 'suspendableStateChange'],
|
emits: [
|
||||||
|
'goto',
|
||||||
|
'dive',
|
||||||
|
'toggleExpanded',
|
||||||
|
'suspendableStateChange',
|
||||||
|
'heightChange',
|
||||||
|
],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
replying: false,
|
replying: false,
|
||||||
|
|
@ -189,7 +194,7 @@ const Status = {
|
||||||
)
|
)
|
||||||
|
|
||||||
// User referenced in post might not be yet present in store
|
// User referenced in post might not be yet present in store
|
||||||
// since their data is not included in status data
|
// since their data is not included in status data, just the id
|
||||||
return user?.statusnet_profile_url
|
return user?.statusnet_profile_url
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -370,7 +375,7 @@ const Status = {
|
||||||
},
|
},
|
||||||
replyToName() {
|
replyToName() {
|
||||||
if (this.mainStatus.in_reply_to_screen_name) {
|
if (this.mainStatus.in_reply_to_screen_name) {
|
||||||
return this.status.in_reply_to_screen_name
|
return this.mainStatus.in_reply_to_screen_name
|
||||||
} else {
|
} else {
|
||||||
const user = useUsersStore().findUser(
|
const user = useUsersStore().findUser(
|
||||||
this.mainStatus.in_reply_to_user_id,
|
this.mainStatus.in_reply_to_user_id,
|
||||||
|
|
@ -387,7 +392,7 @@ const Status = {
|
||||||
return uniqBy(combinedUsers, 'id')
|
return uniqBy(combinedUsers, 'id')
|
||||||
},
|
},
|
||||||
tags() {
|
tags() {
|
||||||
return this.status.tags
|
return [...this.status.tags]
|
||||||
.filter((tagObj) => Object.hasOwn(tagObj, 'name'))
|
.filter((tagObj) => Object.hasOwn(tagObj, 'name'))
|
||||||
.map((tagObj) => tagObj.name)
|
.map((tagObj) => tagObj.name)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
|
|
@ -539,6 +544,7 @@ const Status = {
|
||||||
this.headTailLinks = headTailLinks
|
this.headTailLinks = headTailLinks
|
||||||
},
|
},
|
||||||
toggleThreadDisplay() {
|
toggleThreadDisplay() {
|
||||||
|
// FIXME
|
||||||
this.controlledToggleThreadDisplay()
|
this.controlledToggleThreadDisplay()
|
||||||
},
|
},
|
||||||
scrollIfFocused(focused) {
|
scrollIfFocused(focused) {
|
||||||
|
|
@ -557,8 +563,22 @@ const Status = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onTransitionEnd() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$emit('heightChange')
|
||||||
|
})
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
status: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
this.$emit('heightChange')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
replying() {
|
||||||
|
this.$emit('heightChange')
|
||||||
|
},
|
||||||
focused: function (id) {
|
focused: function (id) {
|
||||||
this.scrollIfFocused(id)
|
this.scrollIfFocused(id)
|
||||||
},
|
},
|
||||||
|
|
@ -584,6 +604,7 @@ const Status = {
|
||||||
},
|
},
|
||||||
isSuspendable: function (suspend) {
|
isSuspendable: function (suspend) {
|
||||||
this.$emit('suspendableStateChange', { id: this.status.id, suspend })
|
this.$emit('suspendableStateChange', { id: this.status.id, suspend })
|
||||||
|
this.$emit('heightChange')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@
|
||||||
|
|
||||||
<span class="heading-right">
|
<span class="heading-right">
|
||||||
<span
|
<span
|
||||||
v-if="showPinned"
|
v-if="mainStatus.pinned"
|
||||||
class="pin"
|
class="pin"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
|
|
@ -454,7 +454,10 @@
|
||||||
</StatusPopover>
|
</StatusPopover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition name="fade">
|
<Transition
|
||||||
|
@after-leave="onTransitionEnd"
|
||||||
|
name="fade"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-if="shouldDisplayFavsAndRepeats"
|
v-if="shouldDisplayFavsAndRepeats"
|
||||||
class="favs-repeated-users"
|
class="favs-repeated-users"
|
||||||
|
|
@ -502,7 +505,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</Transition>
|
||||||
|
|
||||||
<EmojiReactions
|
<EmojiReactions
|
||||||
v-if="(mergedConfig.emojiReactionsOnTimeline || focused) && (!noHeading && !isPreview)"
|
v-if="(mergedConfig.emojiReactionsOnTimeline || focused) && (!noHeading && !isPreview)"
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { mapState } from 'pinia'
|
import { mapState } from 'pinia'
|
||||||
|
|
||||||
import Modal from 'src/components/modal/modal.vue'
|
import Modal from 'src/components/modal/modal.vue'
|
||||||
import StatusContent from 'src/components/status_content/status_content.vue'
|
import Status from 'src/components/status/status.vue'
|
||||||
|
|
||||||
import { useStatusHistoryStore } from 'src/stores/statusHistory.js'
|
import { useStatusHistoryStore } from 'src/stores/statusHistory.js'
|
||||||
|
|
||||||
const StatusHistoryModal = {
|
const StatusHistoryModal = {
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
StatusContent,
|
Status,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@
|
||||||
v-if="historyCount > 0"
|
v-if="historyCount > 0"
|
||||||
class="history-body"
|
class="history-body"
|
||||||
>
|
>
|
||||||
<StatusContent
|
<Status
|
||||||
v-for="status in history"
|
v-for="status in history"
|
||||||
:key="status.id"
|
:key="status.id"
|
||||||
:status="status"
|
:statusoid="status"
|
||||||
:is-preview="true"
|
:is-preview="true"
|
||||||
class="conversation-status status-fadein panel-body"
|
class="conversation-status status-fadein panel-body"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,10 @@ export default {
|
||||||
classesTab.push('active')
|
classesTab.push('active')
|
||||||
classesWrapper.push('active')
|
classesWrapper.push('active')
|
||||||
}
|
}
|
||||||
|
if (props.disabled) {
|
||||||
|
classesTab.push('disabled')
|
||||||
|
classesWrapper.push('disabled')
|
||||||
|
}
|
||||||
if (props.image) {
|
if (props.image) {
|
||||||
return (
|
return (
|
||||||
<div class={classesWrapper.join(' ')}>
|
<div class={classesWrapper.join(' ')}>
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ const ThreadTree = {
|
||||||
totalReplyCount: Object,
|
totalReplyCount: Object,
|
||||||
totalReplyDepth: Object,
|
totalReplyDepth: Object,
|
||||||
},
|
},
|
||||||
emits: ['suspendableStateChange', 'goto', 'dive'],
|
emits: ['suspendableStateChange', 'goto', 'dive', 'heightChange'],
|
||||||
computed: {
|
computed: {
|
||||||
currentReplies() {
|
currentReplies() {
|
||||||
return this.getReplies(this.statusId).map(({ id }) => id)
|
return this.getReplies(this.statusId).map(({ id }) => id)
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
@goto="$emit('goto', statusId)"
|
@goto="$emit('goto', statusId)"
|
||||||
@toggle-expanded="toggleExpanded"
|
@toggle-expanded="toggleExpanded"
|
||||||
@suspendable-state-change="e => $emit('suspendableStateChange', e)"
|
@suspendable-state-change="e => $emit('suspendableStateChange', e)"
|
||||||
|
@height-change="e => $emit('heightChange', e)"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="currentReplies.length > 0 && threadShowing"
|
v-if="currentReplies.length > 0 && threadShowing"
|
||||||
|
|
@ -55,6 +56,7 @@
|
||||||
@goto="(e) => $emit('goto', e)"
|
@goto="(e) => $emit('goto', e)"
|
||||||
@dive="(e) => $emit('dive', e)"
|
@dive="(e) => $emit('dive', e)"
|
||||||
@suspendable-state-change="e => $emit('suspendableStateChange', e)"
|
@suspendable-state-change="e => $emit('suspendableStateChange', e)"
|
||||||
|
@height-change="e => $emit('heightChange', e)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ library.add(faCircleNotch, faCog, faMinus, faArrowUp, faCirclePlus, faCheck)
|
||||||
const Timeline = {
|
const Timeline = {
|
||||||
props: {
|
props: {
|
||||||
timelineRef: Object,
|
timelineRef: Object,
|
||||||
count: Number,
|
|
||||||
footerSlipgate: Object, // reference to an element where we should put our footer
|
footerSlipgate: Object, // reference to an element where we should put our footer
|
||||||
embedded: Boolean,
|
embedded: Boolean,
|
||||||
inProfile: Boolean,
|
inProfile: Boolean,
|
||||||
|
|
@ -59,21 +58,24 @@ const Timeline = {
|
||||||
.map((id) => useStatusesStore().allStatuses.get(id))
|
.map((id) => useStatusesStore().allStatuses.get(id))
|
||||||
.filter(({ pinned }) => (this.skipPinned ? !pinned : true))
|
.filter(({ pinned }) => (this.skipPinned ? !pinned : true))
|
||||||
},
|
},
|
||||||
|
count() {
|
||||||
|
return this.timeline.order.length
|
||||||
|
},
|
||||||
newStatusCount() {
|
newStatusCount() {
|
||||||
return this.timeline.newStatusCount
|
return this.timeline.newStatusCount
|
||||||
},
|
},
|
||||||
showLoadButton() {
|
showLoadButton() {
|
||||||
return this.timeline.newStatusCount > 0 || this.timeline.flushMarker !== 0
|
return this.timeline.newStatusCount > 0 || this.timeline.reloadNeeded
|
||||||
},
|
},
|
||||||
loadButtonString() {
|
loadButtonString() {
|
||||||
if (this.timeline.flushMarker !== 0) {
|
if (this.timeline.reloadNeeded) {
|
||||||
return this.$t('timeline.reload')
|
return this.$t('timeline.reload')
|
||||||
} else {
|
} else {
|
||||||
return `${this.$t('timeline.show_new')} (${this.newStatusCount})`
|
return `${this.$t('timeline.show_new')} (${this.newStatusCount})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mobileLoadButtonString() {
|
mobileLoadButtonString() {
|
||||||
if (this.timeline.flushMarker !== 0) {
|
if (this.timeline.reloadNeeded) {
|
||||||
return '+'
|
return '+'
|
||||||
} else {
|
} else {
|
||||||
return this.newStatusCount > 99 ? '∞' : this.newStatusCount
|
return this.newStatusCount > 99 ? '∞' : this.newStatusCount
|
||||||
|
|
@ -99,6 +101,7 @@ const Timeline = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
statusesToDisplay() {
|
statusesToDisplay() {
|
||||||
|
if (!this.virtualScrollingEnabled) return this.visibleStatusIds
|
||||||
const amount = this.timeline.visibleStatusIds.size
|
const amount = this.timeline.visibleStatusIds.size
|
||||||
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
|
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
|
||||||
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
|
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
|
||||||
|
|
@ -167,9 +170,8 @@ const Timeline = {
|
||||||
if (e.key === '.') this.showNewStatuses()
|
if (e.key === '.') this.showNewStatuses()
|
||||||
},
|
},
|
||||||
showNewStatuses() {
|
showNewStatuses() {
|
||||||
if (this.timeline.flushMarker !== 0) {
|
if (this.timeline.reloadNeeded) {
|
||||||
useTimelinesStore().clearTimeline(this.timelineRef.name)
|
useTimelinesStore().clearTimeline(this.timelineRef.name)
|
||||||
useTimelinesStore().queueFlush(this.timelineRef.name, '')
|
|
||||||
this.fetchOlderStatuses()
|
this.fetchOlderStatuses()
|
||||||
} else {
|
} else {
|
||||||
this.blockClicksTemporarily()
|
this.blockClicksTemporarily()
|
||||||
|
|
@ -190,13 +192,12 @@ const Timeline = {
|
||||||
if (!this.virtualScrollingEnabled) return
|
if (!this.virtualScrollingEnabled) return
|
||||||
|
|
||||||
const statuses = this.$refs.timeline.children
|
const statuses = this.$refs.timeline.children
|
||||||
|
if (statuses.length === 0) return
|
||||||
const cappedScrollIndex = Math.max(
|
const cappedScrollIndex = Math.max(
|
||||||
0,
|
0,
|
||||||
Math.min(this.virtualScrollIndex, statuses.length - 1),
|
Math.min(this.virtualScrollIndex, statuses.length - 1),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (statuses.length === 0) return
|
|
||||||
|
|
||||||
const height = Math.max(document.body.offsetHeight, window.pageYOffset)
|
const height = Math.max(document.body.offsetHeight, window.pageYOffset)
|
||||||
|
|
||||||
const centerOfScreen = window.pageYOffset + window.innerHeight * 0.5
|
const centerOfScreen = window.pageYOffset + window.innerHeight * 0.5
|
||||||
|
|
@ -234,11 +235,11 @@ const Timeline = {
|
||||||
this.virtualScrollIndex = approxIndex
|
this.virtualScrollIndex = approxIndex
|
||||||
},
|
},
|
||||||
scrollLoad() {
|
scrollLoad() {
|
||||||
|
// TODO simplify this logic
|
||||||
const bodyBRect = document.body.getBoundingClientRect()
|
const bodyBRect = document.body.getBoundingClientRect()
|
||||||
const height = Math.max(bodyBRect.height, -bodyBRect.y)
|
const height = Math.max(bodyBRect.height, -bodyBRect.y)
|
||||||
if (
|
if (
|
||||||
!this.timeline.fetcher.loading.value &&
|
!this.timeline.fetcher.loadingOlder.value &&
|
||||||
this.$el.offsetHeight > 0 &&
|
|
||||||
window.innerHeight + window.pageYOffset >= height - 750
|
window.innerHeight + window.pageYOffset >= height - 750
|
||||||
) {
|
) {
|
||||||
this.fetchOlderStatuses()
|
this.fetchOlderStatuses()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['Timeline', classes.root]">
|
<!-- there is a brief moment during logout when old timeline gets forcibly deactivated -->
|
||||||
|
<div v-if="timeline.fetcher" :class="['Timeline', classes.root]">
|
||||||
<div
|
<div
|
||||||
v-if="!embedded"
|
v-if="!embedded"
|
||||||
:class="classes.header"
|
:class="classes.header"
|
||||||
|
|
@ -8,6 +9,16 @@
|
||||||
v-if="!embedded"
|
v-if="!embedded"
|
||||||
:timeline-name="timelineRef.name"
|
:timeline-name="timelineRef.name"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
v-if="timeline.fetcher.loadingNewer"
|
||||||
|
class="loadingIndicator"
|
||||||
|
>
|
||||||
|
<FAIcon
|
||||||
|
fixed-width
|
||||||
|
icon="circle-notch"
|
||||||
|
spin
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<ScrollTopButton />
|
<ScrollTopButton />
|
||||||
<template v-if="mobileLayout">
|
<template v-if="mobileLayout">
|
||||||
<div
|
<div
|
||||||
|
|
@ -84,7 +95,7 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :class="classes.footer">
|
<div v-if="!embedded || footerSlipgate" :class="classes.footer">
|
||||||
<teleport
|
<teleport
|
||||||
:to="footerSlipgate"
|
:to="footerSlipgate"
|
||||||
:disabled="!embedded || !footerSlipgate"
|
:disabled="!embedded || !footerSlipgate"
|
||||||
|
|
@ -102,7 +113,7 @@
|
||||||
{{ $t('timeline.no_more_statuses') }}
|
{{ $t('timeline.no_more_statuses') }}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else-if="!timeline.fetcher.loading"
|
v-else-if="!timeline.fetcher.loadingOlder"
|
||||||
class="button-unstyled -link"
|
class="button-unstyled -link"
|
||||||
@click.prevent="fetchOlderStatuses()"
|
@click.prevent="fetchOlderStatuses()"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const UserAvatar = {
|
||||||
props: {
|
props: {
|
||||||
// UserID of a user to show avatar of
|
// UserID of a user to show avatar of
|
||||||
userId: {
|
userId: {
|
||||||
required: true,
|
required: false, // You can pass null to just render a placeholder
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
// Use less space and use alternative roundness
|
// Use less space and use alternative roundness
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@
|
||||||
|
|
||||||
&.-placeholder {
|
&.-placeholder {
|
||||||
background-color: var(--background);
|
background-color: var(--background);
|
||||||
|
border: 1px solid var(--border)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,6 @@ const UserProfile = {
|
||||||
},
|
},
|
||||||
load(userNameOrId) {
|
load(userNameOrId) {
|
||||||
const loadById = (userId) => {
|
const loadById = (userId) => {
|
||||||
console.log('LOAD', userId)
|
|
||||||
this.userId = userId
|
this.userId = userId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +138,6 @@ const UserProfile = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
switchUser(userNameOrId) {
|
switchUser(userNameOrId) {
|
||||||
console.log('USER SWITCH')
|
|
||||||
this.load(userNameOrId)
|
this.load(userNameOrId)
|
||||||
},
|
},
|
||||||
onTabSwitch(tab) {
|
onTabSwitch(tab) {
|
||||||
|
|
|
||||||
|
|
@ -23,18 +23,14 @@
|
||||||
key="statuses"
|
key="statuses"
|
||||||
class="statuses"
|
class="statuses"
|
||||||
:label="$t('user_card.statuses')"
|
:label="$t('user_card.statuses')"
|
||||||
:count="user.statuses_count"
|
|
||||||
:title="$t('user_profile.timeline_title')"
|
:title="$t('user_profile.timeline_title')"
|
||||||
>
|
>
|
||||||
<!--
|
|
||||||
<Timeline
|
<Timeline
|
||||||
key="statuses"
|
key="statuses"
|
||||||
:timeline-ref="{ name: 'userPinned', argument: userId }"
|
:timeline-ref="{ name: 'userPinned', argument: userId }"
|
||||||
embedded
|
embedded
|
||||||
in-profile
|
in-profile
|
||||||
:footer-slipgate="footerRef"
|
|
||||||
/>
|
/>
|
||||||
-->
|
|
||||||
<Timeline
|
<Timeline
|
||||||
:timeline-ref="{ name: 'user', argument: userId }"
|
:timeline-ref="{ name: 'user', argument: userId }"
|
||||||
embedded
|
embedded
|
||||||
|
|
@ -81,7 +77,6 @@
|
||||||
<Timeline
|
<Timeline
|
||||||
key="media"
|
key="media"
|
||||||
:label="$t('user_card.media')"
|
:label="$t('user_card.media')"
|
||||||
:disabled="media.visibleStatusIds.size === 0"
|
|
||||||
:title="$t('user_card.media')"
|
:title="$t('user_card.media')"
|
||||||
:timeline-ref="{ name: 'media', argument: userId }"
|
:timeline-ref="{ name: 'media', argument: userId }"
|
||||||
embedded
|
embedded
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,12 @@ const UserProfileAdminView = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchStatuses(page) {
|
fetchStatuses(page) {
|
||||||
return useAdminSettingsStore().fetchStatuses({
|
return useAdminSettingsStore()
|
||||||
|
.fetchStatuses({
|
||||||
...this.fetchOptions,
|
...this.fetchOptions,
|
||||||
page,
|
page,
|
||||||
})
|
})
|
||||||
|
.then(({ items }) => items)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -259,7 +259,7 @@ export const parseStatus = (data) => {
|
||||||
output.raw_html = data.content
|
output.raw_html = data.content
|
||||||
output.emojis = data.emojis
|
output.emojis = data.emojis
|
||||||
|
|
||||||
output.tags = data.tags
|
output.tags = new Set(data.tags ?? [])
|
||||||
|
|
||||||
output.edited_at = data.edited_at
|
output.edited_at = data.edited_at
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ export const useAuthFlowStore = defineStore('authFlow', {
|
||||||
this.settings = {}
|
this.settings = {}
|
||||||
},
|
},
|
||||||
async login({ access_token: accessToken }) {
|
async login({ access_token: accessToken }) {
|
||||||
useOAuthStore().setToken(accessToken)
|
await useOAuthStore().setToken(accessToken)
|
||||||
useUsersStore().loginUser(accessToken, { root: true })
|
await useUsersStore().loginUser(accessToken, { root: true })
|
||||||
this.resetState()
|
this.resetState()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,17 @@ const REPLY_VISIBILITY_TIMELINES = new Set([
|
||||||
])
|
])
|
||||||
|
|
||||||
const timelineFetcher = (timeline, argument, credentials) => {
|
const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
const loading = ref(false)
|
const loadingNewer = ref(false)
|
||||||
|
const loadingOlder = ref(false)
|
||||||
const bottomedOut = ref(false)
|
const bottomedOut = ref(false)
|
||||||
const interval = ref(null)
|
const interval = ref(null)
|
||||||
|
|
||||||
const fetchAndUpdate = ({ older = false, showImmediately = false } = {}) => {
|
const fetchAndUpdate = ({ older = false, showImmediately = false } = {}) => {
|
||||||
loading.value = true
|
if (older) {
|
||||||
|
loadingOlder.value = true
|
||||||
|
} else {
|
||||||
|
loadingNewer.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const { hideMutedPosts, replyVisibility } =
|
const { hideMutedPosts, replyVisibility } =
|
||||||
useMergedConfigStore().mergedConfig
|
useMergedConfigStore().mergedConfig
|
||||||
|
|
@ -47,10 +52,11 @@ const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
|
|
||||||
const numStatusesBeforeFetch = timeline.statusIds.size
|
const numStatusesBeforeFetch = timeline.statusIds.size
|
||||||
|
|
||||||
|
if (bottomedOut.value) return
|
||||||
return fetchTimeline(args)
|
return fetchTimeline(args)
|
||||||
.then(({ data: statuses, pagination, timestamp }) => {
|
.then(({ data: statuses, pagination, timestamp }) => {
|
||||||
if (!older && statuses.length >= 20 && numStatusesBeforeFetch > 0) {
|
if (!older && statuses.length >= 20 && numStatusesBeforeFetch > 0) {
|
||||||
useTimelinesStore().queueFlush(timeline.name, timeline.maxId)
|
useTimelinesStore().requireReload(timeline.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (older && statuses.length === 0) {
|
if (older && statuses.length === 0) {
|
||||||
|
|
@ -84,7 +90,11 @@ const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
loading.value = false
|
if (older) {
|
||||||
|
loadingOlder.value = false
|
||||||
|
} else {
|
||||||
|
loadingNewer.value = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +117,8 @@ const timelineFetcher = (timeline, argument, credentials) => {
|
||||||
startFetching,
|
startFetching,
|
||||||
stopFetching,
|
stopFetching,
|
||||||
fetchOlder: () => fetchAndUpdate({ showImmediately: true, older: true }),
|
fetchOlder: () => fetchAndUpdate({ showImmediately: true, older: true }),
|
||||||
loading,
|
loadingOlder,
|
||||||
|
loadingNewer,
|
||||||
bottomedOut,
|
bottomedOut,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export const useOAuthStore = defineStore('oauth', {
|
||||||
this.userToken = token
|
this.userToken = token
|
||||||
},
|
},
|
||||||
clearToken() {
|
clearToken() {
|
||||||
this.userToken = false
|
this.userToken = null
|
||||||
},
|
},
|
||||||
async createApp() {
|
async createApp() {
|
||||||
const instance = useInstanceStore().server
|
const instance = useInstanceStore().server
|
||||||
|
|
|
||||||
|
|
@ -540,10 +540,5 @@ export const useStatusesStore = defineStore('statuses', {
|
||||||
})
|
})
|
||||||
return removed
|
return removed
|
||||||
},
|
},
|
||||||
|
|
||||||
// Misc
|
|
||||||
setVirtualHeight({ statusId, height }) {
|
|
||||||
this.allStatuses.get(statusId).virtualHeight = height
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,6 @@ export const useStreamingStore = defineStore('streaming', {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return [data.id]
|
return [data.id]
|
||||||
default:
|
default:
|
||||||
console.log('UNKNOWN', eventName, eventStream, data)
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const emptyTl = (name, argument = null) => {
|
||||||
maxId: '',
|
maxId: '',
|
||||||
minId: '',
|
minId: '',
|
||||||
streaming: false,
|
streaming: false,
|
||||||
flushMarker: 0,
|
reloadNeeded: false,
|
||||||
fetcher: null,
|
fetcher: null,
|
||||||
socket: null,
|
socket: null,
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +158,7 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
timeline.newStatusCount = 0
|
timeline.newStatusCount = 0
|
||||||
timeline.maxId = ''
|
timeline.maxId = ''
|
||||||
timeline.minId = ''
|
timeline.minId = ''
|
||||||
timeline.flushMarker = 0
|
timeline.reloadNeeded = false
|
||||||
},
|
},
|
||||||
activatePersistents() {
|
activatePersistents() {
|
||||||
TIMELINES.forEach((name) => {
|
TIMELINES.forEach((name) => {
|
||||||
|
|
@ -258,14 +258,24 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
timeline.fetcher.startFetching()
|
timeline.fetcher.startFetching()
|
||||||
},
|
},
|
||||||
stopFetchingTimeline(timelineName, reason) {
|
stopFetchingTimeline(timelineName, reason) {
|
||||||
|
const timeline = this[timelineName]
|
||||||
|
if (timeline.fetcher === null) {
|
||||||
|
console.debug(
|
||||||
|
'[Timelines] Already inactive timeline',
|
||||||
|
timelineName,
|
||||||
|
'Reason:',
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
timeline.fetcher.stopFetching()
|
||||||
console.debug(
|
console.debug(
|
||||||
'[Timelines] Stopped fetching timeline',
|
'[Timelines] Stopped fetching timeline',
|
||||||
timelineName,
|
timelineName,
|
||||||
'Reason:',
|
'Reason:',
|
||||||
reason,
|
reason,
|
||||||
)
|
)
|
||||||
const timeline = this[timelineName]
|
}
|
||||||
timeline.fetcher.stopFetching()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Queues & Timeline manip
|
// Queues & Timeline manip
|
||||||
|
|
@ -295,12 +305,12 @@ export const useTimelinesStore = defineStore('timelines', {
|
||||||
syncOrder(timeline) {
|
syncOrder(timeline) {
|
||||||
timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id))
|
timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id))
|
||||||
},
|
},
|
||||||
queueFlush(timeline, id) {
|
requireReload(timeline, id) {
|
||||||
this[timeline].flushMarker = id
|
this[timeline].reloadNeeded = true
|
||||||
},
|
},
|
||||||
queueFlushAll() {
|
requireReloadAll() {
|
||||||
Object.keys(this).forEach((timeline) => {
|
Object.keys(this).forEach((timeline) => {
|
||||||
this[timeline].flushMarker = this[timeline].maxId
|
this[timeline].reloadNeeded = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -354,11 +354,13 @@ describe('RichContent', () => {
|
||||||
'<span class="MentionLink mention-link">',
|
'<span class="MentionLink mention-link">',
|
||||||
'<a href="lol" class="original" target="_blank">',
|
'<a href="lol" class="original" target="_blank">',
|
||||||
'<span>',
|
'<span>',
|
||||||
|
'<span>',
|
||||||
'https://</span>',
|
'https://</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'lol.tld/</span>',
|
'lol.tld/</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
|
'</span>',
|
||||||
'</a>',
|
'</a>',
|
||||||
'</span>',
|
'</span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
|
|
@ -418,21 +420,25 @@ describe('RichContent', () => {
|
||||||
'<span class="MentionLink mention-link">',
|
'<span class="MentionLink mention-link">',
|
||||||
'<a href="lol" class="original" target="_blank">',
|
'<a href="lol" class="original" target="_blank">',
|
||||||
'<span>',
|
'<span>',
|
||||||
|
'<span>',
|
||||||
'https://</span>',
|
'https://</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'lol.tld/</span>',
|
'lol.tld/</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
|
'</span>',
|
||||||
'</a>',
|
'</a>',
|
||||||
'</span>',
|
'</span>',
|
||||||
'<span class="MentionLink mention-link">',
|
'<span class="MentionLink mention-link">',
|
||||||
'<a href="lol" class="original" target="_blank">',
|
'<a href="lol" class="original" target="_blank">',
|
||||||
'<span>',
|
'<span>',
|
||||||
|
'<span>',
|
||||||
'https://</span>',
|
'https://</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'lol.tld/</span>',
|
'lol.tld/</span>',
|
||||||
'<span>',
|
'<span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
|
'</span>',
|
||||||
'</a>',
|
'</a>',
|
||||||
'</span>',
|
'</span>',
|
||||||
'</span>',
|
'</span>',
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ describe('Timelines store', () => {
|
||||||
const sub = vi.fn()
|
const sub = vi.fn()
|
||||||
useStreamingStore().addSubscriber = sub
|
useStreamingStore().addSubscriber = sub
|
||||||
|
|
||||||
console.log(store.activate)
|
|
||||||
store.activate('friends', undefined, true)
|
store.activate('friends', undefined, true)
|
||||||
|
|
||||||
expect(sub).to.have.been.called
|
expect(sub).to.have.been.called
|
||||||
|
|
@ -34,7 +33,6 @@ describe('Timelines store', () => {
|
||||||
const sub = vi.fn()
|
const sub = vi.fn()
|
||||||
useStreamingStore().addSubscriber = sub
|
useStreamingStore().addSubscriber = sub
|
||||||
|
|
||||||
console.log(store.activate)
|
|
||||||
store.activate('user', '1')
|
store.activate('user', '1')
|
||||||
|
|
||||||
expect(sub).to.not.have.been.called
|
expect(sub).to.not.have.been.called
|
||||||
|
|
|
||||||
|
|
@ -1095,7 +1095,6 @@ describe('Users store', () => {
|
||||||
const store = useUsersStore()
|
const store = useUsersStore()
|
||||||
const { storeAction, apiUrl } = actionKeys(action)
|
const { storeAction, apiUrl } = actionKeys(action)
|
||||||
await store[storeAction](userId)
|
await store[storeAction](userId)
|
||||||
console.log(apiUrl)
|
|
||||||
|
|
||||||
expect(mockFetch).to.have.been.calledWith(
|
expect(mockFetch).to.have.been.calledWith(
|
||||||
USER_API[apiUrl](userId),
|
USER_API[apiUrl](userId),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue