follow requests refactor
This commit is contained in:
parent
e3bc6dbb65
commit
10e3cb588a
12 changed files with 507 additions and 210 deletions
|
|
@ -49,6 +49,12 @@ export default {
|
||||||
MobilePostStatusButton,
|
MobilePostStatusButton,
|
||||||
MobileNav,
|
MobileNav,
|
||||||
DesktopNav,
|
DesktopNav,
|
||||||
|
FollowRequestConfirm: defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
'src/components/follow_request_confirm/follow_request_confirm.vue'
|
||||||
|
),
|
||||||
|
),
|
||||||
SettingsModal: defineAsyncComponent(
|
SettingsModal: defineAsyncComponent(
|
||||||
() => import('src/components/settings_modal/settings_modal.vue'),
|
() => import('src/components/settings_modal/settings_modal.vue'),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@
|
||||||
<UpdateNotification />
|
<UpdateNotification />
|
||||||
<GlobalError />
|
<GlobalError />
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
|
<FollowRequestConfirm v-if="currentUser" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,10 @@ export const MASTODON_FOLLOW_URL = (id) => `/api/v1/accounts/${id}/follow`
|
||||||
export const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
|
export const MASTODON_UNFOLLOW_URL = (id) => `/api/v1/accounts/${id}/unfollow`
|
||||||
|
|
||||||
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
|
const MASTODON_FOLLOW_REQUESTS_URL = '/api/v1/follow_requests'
|
||||||
const MASTODON_APPROVE_USER_URL = (id) =>
|
export const MASTODON_APPROVE_USER_URL = (id) =>
|
||||||
`/api/v1/follow_requests/${id}/authorize`
|
`/api/v1/follow_requests/${id}/authorize`
|
||||||
const MASTODON_DENY_USER_URL = (id) => `/api/v1/follow_requests/${id}/reject`
|
export const MASTODON_DENY_USER_URL = (id) =>
|
||||||
|
`/api/v1/follow_requests/${id}/reject`
|
||||||
const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
|
const MASTODON_USER_RELATIONSHIPS_URL = ({ id, withSuspended }) =>
|
||||||
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
|
`/api/v1/accounts/relationships/${paramsString({ id, withSuspended })}`
|
||||||
export const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
|
export const MASTODON_USER_IN_LISTS = (id) => `/api/v1/accounts/${id}/lists`
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,16 @@
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { mapActions } from 'pinia'
|
||||||
|
|
||||||
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||||
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'
|
|
||||||
|
|
||||||
const FollowRequestCard = {
|
const FollowRequestCard = {
|
||||||
props: ['user'],
|
props: ['user'],
|
||||||
components: {
|
components: {
|
||||||
BasicUserCard,
|
BasicUserCard,
|
||||||
ConfirmModal: defineAsyncComponent(
|
|
||||||
() => import('src/components/confirm_modal/confirm_modal.vue'),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showingApproveConfirmDialog: false,
|
|
||||||
showingDenyConfirmDialog: false,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
findFollowRequestNotificationId() {
|
...mapActions(useFollowRequestsStore, ['approve', 'deny']),
|
||||||
const notif = useNotificationsStore().data.find(
|
|
||||||
(notif) =>
|
|
||||||
notif.from_profile.id === this.user.id &&
|
|
||||||
notif.type === 'follow_request',
|
|
||||||
)
|
|
||||||
return notif?.id
|
|
||||||
},
|
|
||||||
showApproveConfirmDialog() {
|
|
||||||
this.showingApproveConfirmDialog = true
|
|
||||||
},
|
|
||||||
hideApproveConfirmDialog() {
|
|
||||||
this.showingApproveConfirmDialog = false
|
|
||||||
},
|
|
||||||
showDenyConfirmDialog() {
|
|
||||||
this.showingDenyConfirmDialog = true
|
|
||||||
},
|
|
||||||
hideDenyConfirmDialog() {
|
|
||||||
this.showingDenyConfirmDialog = false
|
|
||||||
},
|
|
||||||
approveUser() {
|
|
||||||
if (this.shouldConfirmApprove) {
|
|
||||||
this.showApproveConfirmDialog()
|
|
||||||
} else {
|
|
||||||
this.doApprove()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doApprove() {
|
|
||||||
approveUser({
|
|
||||||
id: this.user.id,
|
|
||||||
credentials: useOAuthStore().token,
|
|
||||||
}).then(() => {
|
|
||||||
const notifId = this.findFollowRequestNotificationId()
|
|
||||||
useFollowRequestsStore().remove(this.user.id)
|
|
||||||
notifId && useNotificationsStore().markSingleNotificationAsSeen(notifId)
|
|
||||||
})
|
|
||||||
this.hideApproveConfirmDialog()
|
|
||||||
},
|
|
||||||
denyUser() {
|
|
||||||
if (this.shouldConfirmDeny) {
|
|
||||||
this.showDenyConfirmDialog()
|
|
||||||
} else {
|
|
||||||
this.doDeny()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doDeny() {
|
|
||||||
denyUser({
|
|
||||||
id: this.user.id,
|
|
||||||
credentials: useOAuthStore().token,
|
|
||||||
}).then(() => {
|
|
||||||
const notifId = this.findFollowRequestNotificationId()
|
|
||||||
useFollowRequestsStore().remove(this.user.id)
|
|
||||||
notifId && useNotificationsStore().markSingleNotificationAsSeen(notifId)
|
|
||||||
})
|
|
||||||
this.hideDenyConfirmDialog()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
mergedConfig() {
|
|
||||||
return useMergedConfigStore().mergedConfig
|
|
||||||
},
|
|
||||||
shouldConfirmApprove() {
|
|
||||||
return this.mergedConfig.modalOnApproveFollow
|
|
||||||
},
|
|
||||||
shouldConfirmDeny() {
|
|
||||||
return this.mergedConfig.modalOnDenyFollow
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,39 +3,17 @@
|
||||||
<div class="follow-request-card-content-container">
|
<div class="follow-request-card-content-container">
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click="approveUser"
|
@click="() => approve(user.id)"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.approve') }}
|
{{ $t('user_card.approve') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn button-default"
|
class="btn button-default"
|
||||||
@click="denyUser"
|
@click="() => deny(user.id)"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.deny') }}
|
{{ $t('user_card.deny') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<teleport to="#modal">
|
|
||||||
<ConfirmModal
|
|
||||||
v-if="showingApproveConfirmDialog"
|
|
||||||
:title="$t('user_card.approve_confirm_title')"
|
|
||||||
:confirm-text="$t('user_card.approve_confirm_accept_button')"
|
|
||||||
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
|
|
||||||
@accepted="doApprove"
|
|
||||||
@cancelled="hideApproveConfirmDialog"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
|
|
||||||
</ConfirmModal>
|
|
||||||
<ConfirmModal
|
|
||||||
v-if="showingDenyConfirmDialog"
|
|
||||||
:title="$t('user_card.deny_confirm_title')"
|
|
||||||
:confirm-text="$t('user_card.deny_confirm_accept_button')"
|
|
||||||
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
|
|
||||||
@accepted="doDeny"
|
|
||||||
@cancelled="hideDenyConfirmDialog"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
|
|
||||||
</ConfirmModal>
|
|
||||||
</teleport>
|
|
||||||
</basic-user-card>
|
</basic-user-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<template>
|
||||||
|
<teleport to="#modal">
|
||||||
|
<div>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="store.showingApproveConfirmDialog"
|
||||||
|
:title="$t('user_card.approve_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.approve_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
|
||||||
|
@accepted="store.doApprove"
|
||||||
|
@cancelled="store.hideApproveConfirmDialog"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
|
||||||
|
</ConfirmModal>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="store.showingDenyConfirmDialog"
|
||||||
|
:title="$t('user_card.deny_confirm_title')"
|
||||||
|
:confirm-text="$t('user_card.deny_confirm_accept_button')"
|
||||||
|
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
|
||||||
|
@accepted="store.doDeny"
|
||||||
|
@cancelled="store.hideDenyConfirmDialog"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
|
||||||
|
</ConfirmModal>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import ConfirmModal from 'src/components/confirm_modal/confirm_modal.vue'
|
||||||
|
|
||||||
|
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const user = computed(() => useUsersStore().findUser(store.tempId))
|
||||||
|
</script>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { mapState } from 'pinia'
|
import { mapActions, mapState } from 'pinia'
|
||||||
import { defineAsyncComponent } from 'vue'
|
|
||||||
|
|
||||||
import Report from 'src/components/report/report.vue'
|
import Report from 'src/components/report/report.vue'
|
||||||
import StatusContent from 'src/components/status_content/status_content.vue'
|
import StatusContent from 'src/components/status_content/status_content.vue'
|
||||||
|
|
@ -16,13 +15,10 @@ import {
|
||||||
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.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 { useStatusesStore } from 'src/stores/statuses.js'
|
||||||
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
import { useUserHighlightStore } from 'src/stores/user_highlight.js'
|
||||||
import { useUsersStore } from 'src/stores/users.js'
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { approveUser, denyUser } from 'src/api/user.js'
|
|
||||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
|
@ -58,8 +54,6 @@ const Notification = {
|
||||||
selecting: false,
|
selecting: false,
|
||||||
statusExpanded: false,
|
statusExpanded: false,
|
||||||
unmuted: false,
|
unmuted: false,
|
||||||
showingApproveConfirmDialog: false,
|
|
||||||
showingDenyConfirmDialog: false,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: ['notification'],
|
props: ['notification'],
|
||||||
|
|
@ -73,9 +67,6 @@ const Notification = {
|
||||||
|
|
||||||
UserPopover,
|
UserPopover,
|
||||||
UserLink,
|
UserLink,
|
||||||
ConfirmModal: defineAsyncComponent(
|
|
||||||
() => import('src/components/confirm_modal/confirm_modal.vue'),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('selectionchange', this.onContentSelect)
|
document.addEventListener('selectionchange', this.onContentSelect)
|
||||||
|
|
@ -125,53 +116,7 @@ const Notification = {
|
||||||
toggleMute() {
|
toggleMute() {
|
||||||
this.unmuted = !this.unmuted
|
this.unmuted = !this.unmuted
|
||||||
},
|
},
|
||||||
showApproveConfirmDialog() {
|
...mapActions(useFollowRequestsStore, ['approve', 'deny']),
|
||||||
this.showingApproveConfirmDialog = true
|
|
||||||
},
|
|
||||||
hideApproveConfirmDialog() {
|
|
||||||
this.showingApproveConfirmDialog = false
|
|
||||||
},
|
|
||||||
showDenyConfirmDialog() {
|
|
||||||
this.showingDenyConfirmDialog = true
|
|
||||||
},
|
|
||||||
hideDenyConfirmDialog() {
|
|
||||||
this.showingDenyConfirmDialog = false
|
|
||||||
},
|
|
||||||
approveUser() {
|
|
||||||
if (this.shouldConfirmApprove) {
|
|
||||||
this.showApproveConfirmDialog()
|
|
||||||
} else {
|
|
||||||
this.doApprove()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doApprove() {
|
|
||||||
approveUser({
|
|
||||||
id: this.user.id,
|
|
||||||
credentials: useOAuthStore().token,
|
|
||||||
})
|
|
||||||
useFollowRequestsStore().remove(this.user.id)
|
|
||||||
useNotificationsStore().markSingleNotificationAsSeen(this.notification.id)
|
|
||||||
this.hideApproveConfirmDialog()
|
|
||||||
},
|
|
||||||
denyUser() {
|
|
||||||
if (this.shouldConfirmDeny) {
|
|
||||||
this.showDenyConfirmDialog()
|
|
||||||
} else {
|
|
||||||
this.doDeny()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
doDeny() {
|
|
||||||
denyUser({
|
|
||||||
id: this.user.id,
|
|
||||||
credentials: useOAuthStore().token,
|
|
||||||
}).then(() => {
|
|
||||||
useNotificationsStore().markSingleNotificationAsSeen(
|
|
||||||
this.notification.id,
|
|
||||||
)
|
|
||||||
useFollowRequestsStore().remove(this.user.id)
|
|
||||||
})
|
|
||||||
this.hideDenyConfirmDialog()
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
status() {
|
status() {
|
||||||
|
|
@ -222,12 +167,6 @@ const Notification = {
|
||||||
scaleMfm() {
|
scaleMfm() {
|
||||||
return this.mergedConfig.scaleMfm
|
return this.mergedConfig.scaleMfm
|
||||||
},
|
},
|
||||||
shouldConfirmApprove() {
|
|
||||||
return this.mergedConfig.modalOnApproveFollow
|
|
||||||
},
|
|
||||||
shouldConfirmDeny() {
|
|
||||||
return this.mergedConfig.modalOnDenyFollow
|
|
||||||
},
|
|
||||||
...mapState(useUsersStore, ['currentUser']),
|
...mapState(useUsersStore, ['currentUser']),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,7 @@
|
||||||
<button
|
<button
|
||||||
class="button-unstyled"
|
class="button-unstyled"
|
||||||
:title="$t('tool_tip.accept_follow_request')"
|
:title="$t('tool_tip.accept_follow_request')"
|
||||||
@click="approveUser()"
|
@click="() => approve(user.id)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="check"
|
icon="check"
|
||||||
|
|
@ -236,7 +236,7 @@
|
||||||
<button
|
<button
|
||||||
class="button-unstyled"
|
class="button-unstyled"
|
||||||
:title="$t('tool_tip.reject_follow_request')"
|
:title="$t('tool_tip.reject_follow_request')"
|
||||||
@click="denyUser()"
|
@click="() => deny(user.id)"
|
||||||
>
|
>
|
||||||
<FAIcon
|
<FAIcon
|
||||||
icon="times"
|
icon="times"
|
||||||
|
|
@ -268,28 +268,6 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<teleport to="#modal">
|
|
||||||
<ConfirmModal
|
|
||||||
v-if="showingApproveConfirmDialog"
|
|
||||||
:title="$t('user_card.approve_confirm_title')"
|
|
||||||
:confirm-text="$t('user_card.approve_confirm_accept_button')"
|
|
||||||
:cancel-text="$t('user_card.approve_confirm_cancel_button')"
|
|
||||||
@accepted="doApprove"
|
|
||||||
@cancelled="hideApproveConfirmDialog"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.approve_confirm', { user: user.screen_name_ui }) }}
|
|
||||||
</ConfirmModal>
|
|
||||||
<ConfirmModal
|
|
||||||
v-if="showingDenyConfirmDialog"
|
|
||||||
:title="$t('user_card.deny_confirm_title')"
|
|
||||||
:confirm-text="$t('user_card.deny_confirm_accept_button')"
|
|
||||||
:cancel-text="$t('user_card.deny_confirm_cancel_button')"
|
|
||||||
@accepted="doDeny"
|
|
||||||
@cancelled="hideDenyConfirmDialog"
|
|
||||||
>
|
|
||||||
{{ $t('user_card.deny_confirm', { user: user.screen_name_ui }) }}
|
|
||||||
</ConfirmModal>
|
|
||||||
</teleport>
|
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ const followRequestFetcher = ({ credentials }) => {
|
||||||
const startFetching = () => {
|
const startFetching = () => {
|
||||||
if (interval.value) throw new Error('Interval already exists!')
|
if (interval.value) throw new Error('Interval already exists!')
|
||||||
|
|
||||||
fetchAndUpdate()
|
|
||||||
|
|
||||||
interval.value = promiseInterval(fetchAndUpdate, 10000)
|
interval.value = promiseInterval(fetchAndUpdate, 10000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
import followRequestFetcher from 'src/stores/fetchers/follow_requests.js'
|
import followRequestFetcher from 'src/stores/fetchers/follow_requests.js'
|
||||||
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
|
import { useNotificationsStore } from 'src/stores/notifications.js'
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
|
||||||
|
import { approveUser, denyUser } from 'src/api/user.js'
|
||||||
|
|
||||||
export const useFollowRequestsStore = defineStore('followRequests', {
|
export const useFollowRequestsStore = defineStore('followRequests', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
fetcher: null,
|
fetcher: null,
|
||||||
requests: new Map(),
|
requests: new Map(),
|
||||||
|
showingApproveConfirmDialog: false,
|
||||||
|
showingDenyConfirmDialog: false,
|
||||||
|
tempId: null,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
followRequestsCount(state) {
|
followRequestsCount(state) {
|
||||||
|
|
@ -14,6 +21,7 @@ export const useFollowRequestsStore = defineStore('followRequests', {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
// Fetcher stuff
|
||||||
startFetching() {
|
startFetching() {
|
||||||
if (this.fetcher) throw new Error('Fetcher already exists!')
|
if (this.fetcher) throw new Error('Fetcher already exists!')
|
||||||
|
|
||||||
|
|
@ -31,8 +39,76 @@ export const useFollowRequestsStore = defineStore('followRequests', {
|
||||||
setFollowRequests(requests) {
|
setFollowRequests(requests) {
|
||||||
this.requests = new Map(requests.map((user) => [user.id, user]))
|
this.requests = new Map(requests.map((user) => [user.id, user]))
|
||||||
},
|
},
|
||||||
remove(id) {
|
|
||||||
|
// Confirm dialogs
|
||||||
|
showApproveConfirmDialog(id) {
|
||||||
|
this.showingApproveConfirmDialog = true
|
||||||
|
this.tempId = id
|
||||||
|
},
|
||||||
|
showDenyConfirmDialog(id) {
|
||||||
|
this.showingDenyConfirmDialog = true
|
||||||
|
this.tempId = id
|
||||||
|
},
|
||||||
|
hideApproveConfirmDialog() {
|
||||||
|
this.showingApproveConfirmDialog = false
|
||||||
|
this.tempId = null
|
||||||
|
},
|
||||||
|
hideDenyConfirmDialog() {
|
||||||
|
this.showingDenyConfirmDialog = false
|
||||||
|
this.tempId = null
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dialog/Instant fork
|
||||||
|
approve(id) {
|
||||||
|
if (useMergedConfigStore().mergedConfig.modalOnApproveFollow) {
|
||||||
|
this.showApproveConfirmDialog(id)
|
||||||
|
} else {
|
||||||
|
this.doApprove(id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deny(id) {
|
||||||
|
if (useMergedConfigStore().mergedConfig.modalOnDenyFollow) {
|
||||||
|
this.showDenyConfirmDialog(id)
|
||||||
|
} else {
|
||||||
|
this.doDeny(id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Actual calls
|
||||||
|
async doApprove(userId) {
|
||||||
|
const id = userId ?? this.tempId
|
||||||
|
this.hideApproveConfirmDialog()
|
||||||
|
|
||||||
|
await approveUser({
|
||||||
|
id,
|
||||||
|
credentials: useOAuthStore().token,
|
||||||
|
})
|
||||||
|
|
||||||
|
const notifId = this.findFollowRequestNotificationId(id)
|
||||||
|
notifId && useNotificationsStore().markSingleNotificationAsSeen(notifId)
|
||||||
this.requests.delete(id)
|
this.requests.delete(id)
|
||||||
},
|
},
|
||||||
|
async doDeny(userId) {
|
||||||
|
const id = userId ?? this.tempId
|
||||||
|
this.hideDenyConfirmDialog()
|
||||||
|
|
||||||
|
await denyUser({
|
||||||
|
id,
|
||||||
|
credentials: useOAuthStore().token,
|
||||||
|
})
|
||||||
|
|
||||||
|
const notifId = this.findFollowRequestNotificationId(id)
|
||||||
|
notifId && useNotificationsStore().markSingleNotificationAsSeen(notifId)
|
||||||
|
this.requests.delete(id)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Utility
|
||||||
|
findFollowRequestNotificationId(userId) {
|
||||||
|
const notif = useNotificationsStore().data.find(
|
||||||
|
(notif) =>
|
||||||
|
notif.from_profile.id === userId && notif.type === 'follow_request',
|
||||||
|
)
|
||||||
|
return notif?.id
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -49,15 +49,39 @@ describe('Drafts store', () => {
|
||||||
it('draftsByTypeAndRefId', async () => {
|
it('draftsByTypeAndRefId', async () => {
|
||||||
const store = useDraftsStore()
|
const store = useDraftsStore()
|
||||||
|
|
||||||
await store.addOrSaveDraft({ id: 1, type: 'edit', refId: 'e1', status: 'draft' })
|
await store.addOrSaveDraft({
|
||||||
await store.addOrSaveDraft({ id: 2, type: 'reply', refId: 'r1', status: 'draft' })
|
id: 1,
|
||||||
|
type: 'edit',
|
||||||
|
refId: 'e1',
|
||||||
|
status: 'draft',
|
||||||
|
})
|
||||||
|
await store.addOrSaveDraft({
|
||||||
|
id: 2,
|
||||||
|
type: 'reply',
|
||||||
|
refId: 'r1',
|
||||||
|
status: 'draft',
|
||||||
|
})
|
||||||
await store.addOrSaveDraft({ id: 3, status: 'draft' })
|
await store.addOrSaveDraft({ id: 3, status: 'draft' })
|
||||||
await store.addOrSaveDraft({ id: 4, type: 'edit', refId: 'e2', status: 'draft' })
|
await store.addOrSaveDraft({
|
||||||
await store.addOrSaveDraft({ id: 5, type: 'reply', refId: 'r2', status: 'draft' })
|
id: 4,
|
||||||
|
type: 'edit',
|
||||||
|
refId: 'e2',
|
||||||
|
status: 'draft',
|
||||||
|
})
|
||||||
|
await store.addOrSaveDraft({
|
||||||
|
id: 5,
|
||||||
|
type: 'reply',
|
||||||
|
refId: 'r2',
|
||||||
|
status: 'draft',
|
||||||
|
})
|
||||||
|
|
||||||
expect(store.draftsByTypeAndRefId).to.be.a('function')
|
expect(store.draftsByTypeAndRefId).to.be.a('function')
|
||||||
expect(store.draftsByTypeAndRefId('edit', 'e1')).to.eql([{ id: 1, type: 'edit', refId: 'e1', status: 'draft' }])
|
expect(store.draftsByTypeAndRefId('edit', 'e1')).to.eql([
|
||||||
expect(store.draftsByTypeAndRefId('reply', 'r1')).to.eql([{ id: 2, type: 'reply', refId: 'r1', status: 'draft' }])
|
{ id: 1, type: 'edit', refId: 'e1', status: 'draft' },
|
||||||
|
])
|
||||||
|
expect(store.draftsByTypeAndRefId('reply', 'r1')).to.eql([
|
||||||
|
{ id: 2, type: 'reply', refId: 'r1', status: 'draft' },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -112,7 +136,7 @@ describe('Drafts store', () => {
|
||||||
[id]: {
|
[id]: {
|
||||||
id,
|
id,
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -128,10 +152,10 @@ describe('Drafts store', () => {
|
||||||
expect(storage.getItem).to.have.been.calledTwice
|
expect(storage.getItem).to.have.been.calledTwice
|
||||||
expect(storage.setItem).to.have.been.calledTwice
|
expect(storage.setItem).to.have.been.calledTwice
|
||||||
expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', {
|
expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', {
|
||||||
'1': {
|
1: {
|
||||||
id: '1',
|
id: '1',
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -157,14 +181,14 @@ describe('Drafts store', () => {
|
||||||
expect(storage.getItem).to.have.been.calledOnce
|
expect(storage.getItem).to.have.been.calledOnce
|
||||||
expect(storage.setItem).to.have.been.calledOnce
|
expect(storage.setItem).to.have.been.calledOnce
|
||||||
expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', {
|
expect(storage.setItem).to.have.been.calledWith('pleroma-fe-drafts', {
|
||||||
'a': {
|
a: {
|
||||||
id: 'a',
|
id: 'a',
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
},
|
},
|
||||||
'c': {
|
c: {
|
||||||
id: 'c',
|
id: 'c',
|
||||||
status: 'draft',
|
status: 'draft',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
338
test/unit/specs/stores/follow_requests.spec.js
Normal file
338
test/unit/specs/stores/follow_requests.spec.js
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
|
import { setActivePinia } from 'pinia'
|
||||||
|
|
||||||
|
import { useFollowRequestsStore } from 'src/stores/follow_requests.js'
|
||||||
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
|
import { useNotificationsStore } from 'src/stores/notifications.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
|
import * as USER_API from 'src/api/user.js'
|
||||||
|
|
||||||
|
const mockMastoAPIUser = ({
|
||||||
|
screen_name = 'u1',
|
||||||
|
name = 'user1',
|
||||||
|
url = 'http://localhost/u1',
|
||||||
|
id = 'u1',
|
||||||
|
} = {}) => ({
|
||||||
|
id,
|
||||||
|
acct: screen_name,
|
||||||
|
display_name: name,
|
||||||
|
fields: [],
|
||||||
|
avatar: '',
|
||||||
|
url,
|
||||||
|
pleroma: {
|
||||||
|
emoji_reactions: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Follow Requests store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||||
|
vi.useFakeTimers()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers()
|
||||||
|
vi.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Getters', () => {
|
||||||
|
it('followRequestsCount returns total number of follow requests', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
|
||||||
|
store.requests = new Map([
|
||||||
|
['1', {}],
|
||||||
|
['2', {}],
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(store).to.have.property('followRequestsCount', 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Actions', () => {
|
||||||
|
describe('Fetcher stuff', () => {
|
||||||
|
it('startFetching should initialize fetcher and fetch some data', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify([mockMastoAPIUser()]), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify([mockMastoAPIUser()]), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
store.startFetching()
|
||||||
|
expect(store.fetcher).to.not.be.null
|
||||||
|
|
||||||
|
await vi.advanceTimersToNextTimerAsync()
|
||||||
|
expect(mockFetch).to.have.been.calledOnce
|
||||||
|
|
||||||
|
await vi.advanceTimersToNextTimerAsync()
|
||||||
|
expect(mockFetch).to.have.been.calledTwice
|
||||||
|
expect(useUsersStore().findUser('u1')).to.not.be.undefined
|
||||||
|
expect(store.requests.get('u1')).to.not.be.undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stopFetching should stop and remove the fetcher', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify([mockMastoAPIUser()]), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify([mockMastoAPIUser()]), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
store.startFetching()
|
||||||
|
expect(store.fetcher).to.not.be.null
|
||||||
|
|
||||||
|
store.stopFetching()
|
||||||
|
expect(store.fetcher).to.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.each(['Approve', 'Deny'])('%s', (intent) => {
|
||||||
|
const doCall = `do${intent}`
|
||||||
|
const apiCall = USER_API[`MASTODON_${intent.toUpperCase()}_USER_URL`]
|
||||||
|
const forkCall = intent.toLowerCase()
|
||||||
|
const forkProperty = `modalOn${intent}Follow`
|
||||||
|
const modalProperty = `showing${intent}ConfirmDialog`
|
||||||
|
const modalCalls = ['show', 'hide'].map(
|
||||||
|
(vis) => `${vis}${intent}ConfirmDialog`,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
describe('Dialog calls', () => {
|
||||||
|
it(`${modalCalls[0]} should show dialog and set tempId`, async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
|
||||||
|
await store[modalCalls[0]]('u13')
|
||||||
|
|
||||||
|
expect(store).to.have.property(modalProperty, true)
|
||||||
|
expect(store).to.have.property('tempId', 'u13')
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`${modalCalls[1]} should hide dialog and clear tempId`, async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
|
||||||
|
await store[modalCalls[1]]()
|
||||||
|
|
||||||
|
expect(store).to.have.property(modalProperty, false)
|
||||||
|
expect(store).to.have.property('tempId', null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Fork calls', () => {
|
||||||
|
it(`Should call ${doCall} if confirmations are disabled (${forkProperty} = false)`, async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const modalSpy = vi.spyOn(store, modalCalls[0]).mockImplementation(() => ({}))
|
||||||
|
const apiSpy = vi.spyOn(store, doCall).mockImplementation(() => ({}))
|
||||||
|
useMergedConfigStore().mergedConfig = { [forkProperty]: false }
|
||||||
|
|
||||||
|
await store[forkCall]('u23')
|
||||||
|
|
||||||
|
expect(modalSpy).to.not.have.been.called
|
||||||
|
expect(apiSpy).to.have.been.calledOnce
|
||||||
|
expect(apiSpy).to.have.been.calledWith('u23')
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`Should call ${modalCalls[0]} if confirmations are enabled (${forkProperty} = true)`, async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const modalSpy = vi.spyOn(store, modalCalls[0]).mockImplementation(() => ({}))
|
||||||
|
const apiSpy = vi.spyOn(store, doCall).mockImplementation(() => ({}))
|
||||||
|
useMergedConfigStore().mergedConfig = { [forkProperty]: true }
|
||||||
|
|
||||||
|
await store[forkCall]('u23')
|
||||||
|
|
||||||
|
expect(modalSpy).to.have.been.called
|
||||||
|
expect(apiSpy).to.not.have.been.calledOnce
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Actual call', () => {
|
||||||
|
it('Should hide popover', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const spy = vi.spyOn(store, modalCalls[1])
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify('ok'), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
await store[doCall]()
|
||||||
|
|
||||||
|
expect(spy).to.have.been.calledOnce
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should call API', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify('ok'), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
await store[doCall]('u99')
|
||||||
|
|
||||||
|
expect(mockFetch).to.have.been.calledOnce
|
||||||
|
expect(mockFetch.mock.calls[0][0]).to.eql(apiCall('u99'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should mark notification as seen', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
store.findFollowRequestNotificationId = vi.fn()
|
||||||
|
store.findFollowRequestNotificationId.mockReturnValue('n91')
|
||||||
|
|
||||||
|
const spy = vi.spyOn(useNotificationsStore(), 'markSingleNotificationAsSeen')
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify('ok'), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
await store[doCall]('u99')
|
||||||
|
|
||||||
|
expect(spy).to.have.been.calledOnce
|
||||||
|
expect(spy).to.have.been.calledWith('n91')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should fallback to tempId if no id is provided', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify('ok'), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
store.tempId = 'u80'
|
||||||
|
await store[doCall]()
|
||||||
|
|
||||||
|
expect(mockFetch).to.have.been.calledOnce
|
||||||
|
expect(mockFetch.mock.calls[0][0]).to.eql(apiCall('u80'))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should remove request from cache', async () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
const mockFetch = vi.fn()
|
||||||
|
|
||||||
|
store.requests.set('u95', { id: 'u95' })
|
||||||
|
store.requests.set('u96', { id: 'u96' })
|
||||||
|
store.requests.set('u97', { id: 'u97' })
|
||||||
|
store.requests.set('u98', { id: 'u98' })
|
||||||
|
store.requests.set('u99', { id: 'u99' })
|
||||||
|
|
||||||
|
mockFetch.mockResolvedValueOnce(
|
||||||
|
new Response(JSON.stringify('ok'), {
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', mockFetch)
|
||||||
|
|
||||||
|
await store[doCall]('u99')
|
||||||
|
|
||||||
|
expect(store.requests).to.have.length(4)
|
||||||
|
expect(store.requests.get('u99')).to.be.undefined
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Utility', () => {
|
||||||
|
describe('findFollowRequestNotificationId', () => {
|
||||||
|
it('should search notifications store for relevant notification', () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
|
||||||
|
useNotificationsStore().data = [
|
||||||
|
{
|
||||||
|
id: 'n4',
|
||||||
|
from_profile: { id: 'u3' },
|
||||||
|
type: 'follow_request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n3',
|
||||||
|
from_profile: { id: 'u2' },
|
||||||
|
type: 'repeat',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n2',
|
||||||
|
from_profile: { id: 'u1' },
|
||||||
|
type: 'follow_request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n1',
|
||||||
|
from_profile: { id: 'u1' },
|
||||||
|
type: 'favorite',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = store.findFollowRequestNotificationId('u1')
|
||||||
|
|
||||||
|
expect(result).to.have.eql('n2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("shouldn't crash if there is no notification available", () => {
|
||||||
|
const store = useFollowRequestsStore()
|
||||||
|
|
||||||
|
useNotificationsStore().data = [
|
||||||
|
{
|
||||||
|
id: 'n4',
|
||||||
|
from_profile: { id: 'u3' },
|
||||||
|
type: 'follow_request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n3',
|
||||||
|
from_profile: { id: 'u2' },
|
||||||
|
type: 'repeat',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n2',
|
||||||
|
from_profile: { id: 'u1' },
|
||||||
|
type: 'follow_request',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n1',
|
||||||
|
from_profile: { id: 'u1' },
|
||||||
|
type: 'favorite',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const result = store.findFollowRequestNotificationId('u5')
|
||||||
|
|
||||||
|
expect(result).to.have.eql(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue