feat: sent follow request list api and user interface

Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
laozhoubuluo 2024-03-01 00:40:20 +00:00 committed by naskya
parent d2ed7fcb70
commit d709030580
10 changed files with 234 additions and 0 deletions

View file

@ -4,6 +4,7 @@ Breaking changes are indicated by the :warning: icon.
## Unreleased
- Added `following/requests/sent` endpoint for added Sent Following Requests List Feature.
- The following endpoints are added:
- `reply-mute/create`
- `reply-mute/delete`

View file

@ -109,6 +109,7 @@ defaultNoteVisibility: "Default visibility"
follow: "Follow"
followRequest: "Follow Request"
followRequests: "Follow requests"
sentFollowRequests: "Sent follow requests"
unfollow: "Unfollow"
followRequestPending: "Follow request pending"
enterEmoji: "Enter an emoji"
@ -534,6 +535,7 @@ existingAccount: "Existing account"
regenerate: "Regenerate"
fontSize: "Font size"
noFollowRequests: "You don't have any pending follow requests"
noSentFollowRequests: "You don't have any sent follow requests"
openImageInNewTab: "Open images in new tab"
dashboard: "Dashboard"
local: "Local"

View file

@ -91,6 +91,7 @@ defaultNoteVisibility: "默认可见性"
follow: "关注"
followRequest: "关注请求"
followRequests: "关注请求"
sentFollowRequests: "待回应的关注请求"
unfollow: "取消关注"
followRequestPending: "关注请求待批准"
enterEmoji: "输入表情符号"
@ -470,6 +471,7 @@ existingAccount: "现有的账号"
regenerate: "重新生成"
fontSize: "字体大小"
noFollowRequests: "没有待批准的关注申请"
noSentFollowRequests: "没有待回应的关注请求"
openImageInNewTab: "在新标签页中打开图片"
dashboard: "管理面板"
local: "本地"

View file

@ -91,6 +91,7 @@ defaultNoteVisibility: "預設可見性"
follow: "追隨"
followRequest: "追隨請求"
followRequests: "追隨請求"
sentFollowRequests: "待回應的追隨請求"
unfollow: "取消追隨"
followRequestPending: "追隨許可批准中"
enterEmoji: "輸入表情符號"
@ -469,6 +470,7 @@ existingAccount: "現有帳戶"
regenerate: "再生"
fontSize: "字體大小"
noFollowRequests: "沒有要求跟隨您的申請"
noSentFollowRequests: "沒有待回應的跟隨申請"
openImageInNewTab: "於新分頁中開啟圖片"
dashboard: "儀表板"
local: "本地"

View file

@ -144,6 +144,7 @@ import * as ep___following_invalidate from "./endpoints/following/invalidate.js"
import * as ep___following_requests_accept from "./endpoints/following/requests/accept.js";
import * as ep___following_requests_cancel from "./endpoints/following/requests/cancel.js";
import * as ep___following_requests_list from "./endpoints/following/requests/list.js";
import * as ep___following_requests_sent from "./endpoints/following/requests/sent.js";
import * as ep___following_requests_reject from "./endpoints/following/requests/reject.js";
import * as ep___gallery_featured from "./endpoints/gallery/featured.js";
import * as ep___gallery_popular from "./endpoints/gallery/popular.js";
@ -492,6 +493,7 @@ const eps = [
["following/requests/accept", ep___following_requests_accept],
["following/requests/cancel", ep___following_requests_cancel],
["following/requests/list", ep___following_requests_list],
["following/requests/sent", ep___following_requests_sent],
["following/requests/reject", ep___following_requests_reject],
["gallery/featured", ep___gallery_featured],
["gallery/popular", ep___gallery_popular],

View file

@ -0,0 +1,55 @@
import define from "@/server/api/define.js";
import { FollowRequests } from "@/models/index.js";
export const meta = {
tags: ["following", "users"],
requireCredential: true,
kind: "read:following",
res: {
type: "array",
optional: false,
nullable: false,
items: {
type: "object",
optional: false,
nullable: false,
properties: {
id: {
type: "string",
optional: false,
nullable: false,
format: "id",
},
follower: {
type: "object",
optional: false,
nullable: false,
ref: "UserLite",
},
followee: {
type: "object",
optional: false,
nullable: false,
ref: "UserLite",
},
},
},
},
} as const;
export const paramDef = {
type: "object",
properties: {},
required: [],
} as const;
export default define(meta, paramDef, async (ps, user) => {
const reqs = await FollowRequests.findBy({
followerId: user.id,
});
return await Promise.all(reqs.map((req) => FollowRequests.pack(req)));
});

View file

@ -0,0 +1,160 @@
<template>
<MkStickyContainer>
<template #header><MkPageHeader /></template>
<MkSpacer :content-max="800">
<MkPagination ref="paginationComponent" :pagination="pagination">
<template #empty>
<div class="_fullinfo">
<img
src="/static-assets/badges/info.webp"
aria-label="none"
class="_ghost"
/>
<div>{{ i18n.ts.noSentFollowRequests }}</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.followee"
:show-indicator="true"
disable-link
/>
<div class="body">
<div class="name">
<MkA
v-user-preview="req.followee.id"
class="name"
:to="userPage(req.followee)"
><MkUserName :user="req.followee"
/></MkA>
<p class="acct">
@{{ acct.toString(req.followee) }}
</p>
</div>
<div
v-if="req.followee.description"
class="description"
:title="req.followee.description"
>
<Mfm
:text="req.followee.description"
:is-note="false"
:author="req.followee"
:i="$i"
:custom-emojis="req.followee.emojis"
:plain="true"
:nowrap="true"
/>
</div>
</div>
</div>
</div>
</template>
</MkPagination>
</MkSpacer>
</MkStickyContainer>
</template>
<script lang="ts" setup>
import { computed, ref } from "vue";
import MkPagination from "@/components/MkPagination.vue";
import { acct } from "firefish-js";
import { userPage } from "@/filters/user";
import * as os from "@/os";
import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata";
import { $i } from "@/reactiveAccount";
import icon from "@/scripts/icon";
const paginationComponent = ref<InstanceType<typeof MkPagination>>();
const pagination = {
endpoint: "following/requests/sent" as const,
limit: 10,
noPaging: true,
};
definePageMetadata(
computed(() => ({
title: i18n.ts.sentFollowRequests,
icon: `${icon("ph-hand-waving")}`,
})),
);
</script>
<style lang="scss" scoped>
.mk-follow-requests {
> .user {
display: flex;
padding: 16px;
margin: 10px 0 auto;
> .avatar {
display: block;
flex-shrink: 0;
margin: 0 12px 0 0;
width: 42px;
height: 42px;
border-radius: 8px;
}
> .body {
display: flex;
width: calc(100% - 54px);
position: relative;
> .name {
width: 45%;
@media (max-width: 500px) {
width: 100%;
}
> .name,
> .acct {
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
margin: 0;
}
> .name {
font-size: 16px;
line-height: 24px;
}
> .acct {
font-size: 15px;
line-height: 16px;
opacity: 0.7;
}
}
> .description {
width: 55%;
line-height: 42px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0.7;
font-size: 14px;
padding-right: 40px;
padding-left: 8px;
box-sizing: border-box;
@media (max-width: 500px) {
display: none;
}
}
}
}
}
</style>

View file

@ -4,6 +4,10 @@
i18n.ts.accountInfo
}}</FormLink>
<FormLink to="/my/follow-requests/sent" class="_formBlock">{{
i18n.ts.sentFollowRequests
}}</FormLink>
<FormLink to="/registry" class="_formBlock"
><template #icon><i :class="icon('ph-gear-six')"></i></template
>{{ i18n.ts.registry }}</FormLink

View file

@ -587,6 +587,11 @@ export const routes = [
component: page(() => import("./pages/follow-requests.vue")),
loginRequired: true,
},
{
path: "/my/follow-requests/sent",
component: page(() => import("./pages/follow-requests-sent.vue")),
loginRequired: true,
},
{
path: "/my/lists/:listId",
component: page(() => import("./pages/my-lists/list.vue")),

View file

@ -399,6 +399,7 @@ export type Endpoints = {
"following/requests/accept": { req: { userId: User["id"] }; res: null };
"following/requests/cancel": { req: { userId: User["id"] }; res: User };
"following/requests/list": { req: NoParams; res: FollowRequest[] };
"following/requests/sent": { req: NoParams; res: FollowRequest[] };
"following/requests/reject": { req: { userId: User["id"] }; res: null };
// gallery