Merge 7ffb0f863b
into 794cb9ffe2
This commit is contained in:
commit
02a250397a
|
@ -16,6 +16,8 @@
|
|||
- 認証するアカウントを切り替えられるように
|
||||
- Enhance: Self-XSS防止用の警告を追加
|
||||
- Enhance: カタルーニャ語 (ca-ES) に対応
|
||||
- Enhance: 過去に送信したフォローリクエストを確認できるように
|
||||
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/663)
|
||||
- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
|
||||
- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768)
|
||||
|
|
|
@ -187,6 +187,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';
|
||||
|
@ -574,6 +575,7 @@ const $following_invalidate: Provider = { provide: 'ep:following/invalidate', us
|
|||
const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default };
|
||||
const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default };
|
||||
const $following_requests_list: Provider = { provide: 'ep:following/requests/list', useClass: ep___following_requests_list.default };
|
||||
const $following_requests_sent: Provider = { provide: 'ep:following/requests/sent', useClass: ep___following_requests_sent.default };
|
||||
const $following_requests_reject: Provider = { provide: 'ep:following/requests/reject', useClass: ep___following_requests_reject.default };
|
||||
const $gallery_featured: Provider = { provide: 'ep:gallery/featured', useClass: ep___gallery_featured.default };
|
||||
const $gallery_popular: Provider = { provide: 'ep:gallery/popular', useClass: ep___gallery_popular.default };
|
||||
|
@ -965,6 +967,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
|||
$following_requests_accept,
|
||||
$following_requests_cancel,
|
||||
$following_requests_list,
|
||||
$following_requests_sent,
|
||||
$following_requests_reject,
|
||||
$gallery_featured,
|
||||
$gallery_popular,
|
||||
|
|
|
@ -193,6 +193,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';
|
||||
|
@ -578,6 +579,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],
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import type { FollowRequestsRepository } from '@/models/_.js';
|
||||
import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['following', 'account'],
|
||||
|
||||
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: {
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.followRequestsRepository)
|
||||
private followRequestsRepository: FollowRequestsRepository,
|
||||
|
||||
private followRequestEntityService: FollowRequestEntityService,
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId)
|
||||
.andWhere('request.followerId = :meId', { meId: me.id });
|
||||
|
||||
const requests = await query
|
||||
.limit(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.followRequestEntityService.packMany(requests, me);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -5,69 +5,107 @@ 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 * as Misskey from 'misskey-js';
|
||||
import { shallowRef, computed, ref } from 'vue';
|
||||
import MkPagination 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,
|
||||
limit: 10,
|
||||
};
|
||||
const pagination = computed(() => tab.value === 'list'
|
||||
? {
|
||||
endpoint: 'following/requests/list' as const,
|
||||
limit: 10,
|
||||
}
|
||||
: {
|
||||
endpoint: 'following/requests/sent' as const,
|
||||
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.followRequests,
|
||||
icon: 'ti ti-mail',
|
||||
}, {
|
||||
key: 'sent',
|
||||
title: i18n.ts.followRequestPending,
|
||||
icon: 'ti ti-send',
|
||||
},
|
||||
]);
|
||||
|
||||
const tab = ref($i?.isLocked ? 'list' : 'sent');
|
||||
|
||||
definePageMetadata(() => ({
|
||||
title: i18n.ts.followRequests,
|
||||
|
|
|
@ -1504,6 +1504,8 @@ declare namespace entities {
|
|||
FollowingRequestsCancelResponse,
|
||||
FollowingRequestsListRequest,
|
||||
FollowingRequestsListResponse,
|
||||
FollowingRequestsSentRequest,
|
||||
FollowingRequestsSentResponse,
|
||||
FollowingRequestsRejectRequest,
|
||||
GalleryFeaturedRequest,
|
||||
GalleryFeaturedResponse,
|
||||
|
@ -2018,6 +2020,12 @@ type FollowingRequestsListResponse = operations['following___requests___list']['
|
|||
// @public (undocumented)
|
||||
type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type FollowingRequestsSentRequest = operations['following___requests___sent']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type FollowingRequestsSentResponse = operations['following___requests___sent']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json'];
|
||||
|
||||
|
|
|
@ -2008,6 +2008,17 @@ declare module '../api.js' {
|
|||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:following*
|
||||
*/
|
||||
request<E extends 'following/requests/sent', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
|
|
@ -279,6 +279,8 @@ import type {
|
|||
FollowingRequestsCancelResponse,
|
||||
FollowingRequestsListRequest,
|
||||
FollowingRequestsListResponse,
|
||||
FollowingRequestsSentRequest,
|
||||
FollowingRequestsSentResponse,
|
||||
FollowingRequestsRejectRequest,
|
||||
GalleryFeaturedRequest,
|
||||
GalleryFeaturedResponse,
|
||||
|
@ -761,6 +763,7 @@ export type Endpoints = {
|
|||
'following/requests/accept': { req: FollowingRequestsAcceptRequest; res: EmptyResponse };
|
||||
'following/requests/cancel': { req: FollowingRequestsCancelRequest; res: FollowingRequestsCancelResponse };
|
||||
'following/requests/list': { req: FollowingRequestsListRequest; res: FollowingRequestsListResponse };
|
||||
'following/requests/sent': { req: FollowingRequestsSentRequest; res: FollowingRequestsSentResponse };
|
||||
'following/requests/reject': { req: FollowingRequestsRejectRequest; res: EmptyResponse };
|
||||
'gallery/featured': { req: GalleryFeaturedRequest; res: GalleryFeaturedResponse };
|
||||
'gallery/popular': { req: EmptyRequest; res: GalleryPopularResponse };
|
||||
|
|
|
@ -282,6 +282,8 @@ export type FollowingRequestsCancelRequest = operations['following___requests___
|
|||
export type FollowingRequestsCancelResponse = operations['following___requests___cancel']['responses']['200']['content']['application/json'];
|
||||
export type FollowingRequestsListRequest = operations['following___requests___list']['requestBody']['content']['application/json'];
|
||||
export type FollowingRequestsListResponse = operations['following___requests___list']['responses']['200']['content']['application/json'];
|
||||
export type FollowingRequestsSentRequest = operations['following___requests___sent']['requestBody']['content']['application/json'];
|
||||
export type FollowingRequestsSentResponse = operations['following___requests___sent']['responses']['200']['content']['application/json'];
|
||||
export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json'];
|
||||
export type GalleryFeaturedRequest = operations['gallery___featured']['requestBody']['content']['application/json'];
|
||||
export type GalleryFeaturedResponse = operations['gallery___featured']['responses']['200']['content']['application/json'];
|
||||
|
|
|
@ -1753,6 +1753,15 @@ export type paths = {
|
|||
*/
|
||||
post: operations['following___requests___list'];
|
||||
};
|
||||
'/following/requests/sent': {
|
||||
/**
|
||||
* following/requests/sent
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:following*
|
||||
*/
|
||||
post: operations['following___requests___sent'];
|
||||
};
|
||||
'/following/requests/reject': {
|
||||
/**
|
||||
* following/requests/reject
|
||||
|
@ -16040,6 +16049,69 @@ export type operations = {
|
|||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* following/requests/sent
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *read:following*
|
||||
*/
|
||||
following___requests___sent: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
sinceId?: string;
|
||||
/** Format: misskey:id */
|
||||
untilId?: string;
|
||||
/** @default 10 */
|
||||
limit?: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: id */
|
||||
id: string;
|
||||
follower: components['schemas']['UserLite'];
|
||||
followee: components['schemas']['UserLite'];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Authentication error */
|
||||
401: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden error */
|
||||
403: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description I'm Ai */
|
||||
418: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* following/requests/reject
|
||||
* @description No description provided.
|
||||
|
|
Loading…
Reference in New Issue