feat: 送信したフォローリクエストを確認できるように (#14856)

* FEAT: Allow users to view pending follow requests they sent

This commit implements the `following/requests/sent` interface firstly
implemented on Firefish, and provides a UI interface to view the pending
follow requests users sent.

* ux: should not show follow requests tab when have no pending sent follow req

* fix default followreq tab

* fix default followreq tab

* restore missing hasPendingReceivedFollowRequest in navbar

* refactor

* use tabler icons

* tweak design

* Revert "ux: should not show follow requests tab when have no pending sent follow req"

This reverts commit e580b92c37.

* Update Changelog

* Update Changelog

* change tab titles

---------

Co-authored-by: Lhc_fl <lhcfl@outlook.com>
Co-authored-by: Hazelnoot <acomputerdog@gmail.com>
This commit is contained in:
かっこかり
2024-11-15 17:30:54 +09:00
committed by GitHub
parent e26e24b610
commit c0d1682604
14 changed files with 272 additions and 38 deletions

View File

@@ -91,7 +91,10 @@ async function onClick() {
text: i18n.tsx.unfollowConfirm({ name: props.user.name || props.user.username }),
});
if (canceled) return;
if (canceled) {
wait.value = false;
return;
}
await misskeyApi('following/delete', {
userId: props.user.id,
@@ -125,7 +128,10 @@ async function onClick() {
});
hasPendingFollowRequestFromYou.value = true;
if ($i == null) return;
if ($i == null) {
wait.value = false;
return;
}
claimAchievement('following1');

View File

@@ -40,7 +40,6 @@ export const navbarItemDef = reactive({
followRequests: {
title: i18n.ts.followRequests,
icon: 'ti ti-user-plus',
show: computed(() => $i != null && $i.isLocked),
indicated: computed(() => $i != null && $i.hasPendingReceivedFollowRequest),
to: '/my/follow-requests',
},

View File

@@ -5,69 +5,104 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkStickyContainer>
<template #header><MkPageHeader/></template>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="800">
<MkPagination ref="paginationComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.noFollowRequests }}</div>
</div>
</template>
<template #default="{items}">
<div class="mk-follow-requests">
<div v-for="req in items" :key="req.id" class="user _panel">
<MkAvatar class="avatar" :user="req.follower" indicator link preview/>
<div class="body">
<div class="name">
<MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>
<p class="acct">@{{ acct(req.follower) }}</p>
</div>
<div class="commands">
<MkButton class="command" rounded primary @click="accept(req.follower)"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
<MkButton class="command" rounded danger @click="reject(req.follower)"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<div :key="tab" class="_gaps">
<MkPagination ref="paginationComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img :src="infoImageUrl" class="_ghost"/>
<div>{{ i18n.ts.noFollowRequests }}</div>
</div>
</template>
<template #default="{items}">
<div class="mk-follow-requests">
<div v-for="req in items" :key="req.id" class="user _panel">
<MkAvatar class="avatar" :user="displayUser(req)" indicator link preview/>
<div class="body">
<div class="name">
<MkA v-user-preview="displayUser(req).id" class="name" :to="userPage(displayUser(req))"><MkUserName :user="displayUser(req)"/></MkA>
<p class="acct">@{{ acct(displayUser(req)) }}</p>
</div>
<div v-if="tab === 'list'" class="commands">
<MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
<MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
</div>
<div v-else class="commands">
<MkButton class="command" rounded danger @click="cancel(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.cancel }}</MkButton>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</MkPagination>
</template>
</MkPagination>
</div>
</MkHorizontalSwipe>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { shallowRef, computed } from 'vue';
import MkPagination from '@/components/MkPagination.vue';
import * as Misskey from 'misskey-js';
import { shallowRef, computed, ref } from 'vue';
import MkPagination, { type Paging } from '@/components/MkPagination.vue';
import MkButton from '@/components/MkButton.vue';
import { userPage, acct } from '@/filters/user.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { infoImageUrl } from '@/instance.js';
import { $i } from '@/account.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
const pagination = {
endpoint: 'following/requests/list' as const,
const pagination = computed<Paging>(() => tab.value === 'list' ? {
endpoint: 'following/requests/list',
limit: 10,
};
} : {
endpoint: 'following/requests/sent',
limit: 10,
});
function accept(user) {
misskeyApi('following/requests/accept', { userId: user.id }).then(() => {
function accept(user: Misskey.entities.UserLite) {
os.apiWithDialog('following/requests/accept', { userId: user.id }).then(() => {
paginationComponent.value?.reload();
});
}
function reject(user) {
misskeyApi('following/requests/reject', { userId: user.id }).then(() => {
function reject(user: Misskey.entities.UserLite) {
os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => {
paginationComponent.value?.reload();
});
}
function cancel(user: Misskey.entities.UserLite) {
os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => {
paginationComponent.value?.reload();
});
}
function displayUser(req) {
return tab.value === 'list' ? req.follower : req.followee;
}
const headerActions = computed(() => []);
const headerTabs = computed(() => []);
const headerTabs = computed(() => [
{
key: 'list',
title: i18n.ts._followRequest.recieved,
icon: 'ti ti-mail',
}, {
key: 'sent',
title: i18n.ts._followRequest.sent,
icon: 'ti ti-send',
},
]);
const tab = ref($i?.isLocked ? 'list' : 'sent');
definePageMetadata(() => ({
title: i18n.ts.followRequests,