enhance: 招待されているが参加していないルームを開いたときに、招待を承認するかどうか尋ねるように

This commit is contained in:
syuilo 2025-05-10 11:25:00 +09:00
parent e1cd7c94fb
commit 0a0d42bb48
7 changed files with 78 additions and 10 deletions

View File

@ -15,6 +15,7 @@
- 従来のWebsocket接続を行うモードはリアルタイムモードとして再定義されました - 従来のWebsocket接続を行うモードはリアルタイムモードとして再定義されました
- チャットなど、一部の機能は引き続き設定に関わらずWebsocket接続が行われます - チャットなど、一部の機能は引き続き設定に関わらずWebsocket接続が行われます
- Enhance: メモリ使用量を軽減しました - Enhance: メモリ使用量を軽減しました
- Enhance: 招待されているが参加していないルームを開いたときに、招待を承認するかどうか尋ねるように
- Enhance: リプライ元にアンケートがあることが表示されるように - Enhance: リプライ元にアンケートがあることが表示されるように
- Enhance: ノートのサーバー情報のデザインを改善・パフォーマンス向上 - Enhance: ノートのサーバー情報のデザインを改善・パフォーマンス向上
(Based on https://github.com/taiyme/misskey/pull/198, https://github.com/taiyme/misskey/pull/211, https://github.com/taiyme/misskey/pull/283) (Based on https://github.com/taiyme/misskey/pull/198, https://github.com/taiyme/misskey/pull/211, https://github.com/taiyme/misskey/pull/283)
@ -22,6 +23,7 @@
### Server ### Server
- Enhance: ノートのレスポンスにアンケートが添付されているかどうかを示すフラグ`hasPoll`を追加 - Enhance: ノートのレスポンスにアンケートが添付されているかどうかを示すフラグ`hasPoll`を追加
- Enhance: チャットルームのレスポンスに招待されているかどうかを示すフラグ`invitationExists`を追加
- Fix: チャットルームが削除された場合・チャットルームから抜けた場合に、未読状態が残り続けることがあるのを修正 - Fix: チャットルームが削除された場合・チャットルームから抜けた場合に、未読状態が残り続けることがあるのを修正
- Fix: ユーザ除外アンテナをインポートできない問題を修正 - Fix: ユーザ除外アンテナをインポートできない問題を修正
- Fix: アンテナのセンシティブなチャンネルのノートを含むかどうかの情報がエクスポートされない問題を修正 - Fix: アンテナのセンシティブなチャンネルのノートを含むかどうかの情報がエクスポートされない問題を修正

8
locales/index.d.ts vendored
View File

@ -5555,6 +5555,14 @@ export interface Locale extends ILocale {
* 使 * 使
*/ */
"cannotChatWithTheUser_description": string; "cannotChatWithTheUser_description": string;
/**
*
*/
"youAreNotAMemberOfThisRoomButInvited": string;
/**
*
*/
"doYouAcceptInvitation": string;
/** /**
* *
*/ */

View File

@ -1385,6 +1385,8 @@ _chat:
chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。" chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。"
cannotChatWithTheUser: "このユーザーとのチャットを開始できません" cannotChatWithTheUser: "このユーザーとのチャットを開始できません"
cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。" cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。"
youAreNotAMemberOfThisRoomButInvited: "あなたはこのルームの参加者ではありませんが、招待が届いています。参加するには、招待を承認してください。"
doYouAcceptInvitation: "招待を承認しますか?"
chatWithThisUser: "チャットする" chatWithThisUser: "チャットする"
thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。" thisUserAllowsChatOnlyFromFollowers: "このユーザーはフォロワーからのみチャットを受け付けています。"
thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。" thisUserAllowsChatOnlyFromFollowing: "このユーザーは、このユーザーがフォローしているユーザーからのみチャットを受け付けています。"

View File

@ -238,13 +238,15 @@ export class ChatEntityService {
options?: { options?: {
_hint_?: { _hint_?: {
packedOwners: Map<MiChatRoom['id'], Packed<'UserLite'>>; packedOwners: Map<MiChatRoom['id'], Packed<'UserLite'>>;
memberships?: Map<MiChatRoom['id'], MiChatRoomMembership | null | undefined>; myMemberships?: Map<MiChatRoom['id'], MiChatRoomMembership | null | undefined>;
myInvitations?: Map<MiChatRoom['id'], MiChatRoomInvitation | null | undefined>;
}; };
}, },
): Promise<Packed<'ChatRoom'>> { ): Promise<Packed<'ChatRoom'>> {
const room = typeof src === 'object' ? src : await this.chatRoomsRepository.findOneByOrFail({ id: src }); const room = typeof src === 'object' ? src : await this.chatRoomsRepository.findOneByOrFail({ id: src });
const membership = me && me.id !== room.ownerId ? (options?._hint_?.memberships?.get(room.id) ?? await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null; const membership = me && me.id !== room.ownerId ? (options?._hint_?.myMemberships?.get(room.id) ?? await this.chatRoomMembershipsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null;
const invitation = me && me.id !== room.ownerId ? (options?._hint_?.myInvitations?.get(room.id) ?? await this.chatRoomInvitationsRepository.findOneBy({ roomId: room.id, userId: me.id })) : null;
return { return {
id: room.id, id: room.id,
@ -254,6 +256,7 @@ export class ChatEntityService {
ownerId: room.ownerId, ownerId: room.ownerId,
owner: options?._hint_?.packedOwners.get(room.ownerId) ?? await this.userEntityService.pack(room.owner ?? room.ownerId, me), owner: options?._hint_?.packedOwners.get(room.ownerId) ?? await this.userEntityService.pack(room.owner ?? room.ownerId, me),
isMuted: membership != null ? membership.isMuted : false, isMuted: membership != null ? membership.isMuted : false,
invitationExists: invitation != null,
}; };
} }
@ -278,7 +281,7 @@ export class ChatEntityService {
const owners = _rooms.map(x => x.owner ?? x.ownerId); const owners = _rooms.map(x => x.owner ?? x.ownerId);
const [packedOwners, memberships] = await Promise.all([ const [packedOwners, myMemberships, myInvitations] = await Promise.all([
this.userEntityService.packMany(owners, me) this.userEntityService.packMany(owners, me)
.then(users => new Map(users.map(u => [u.id, u]))), .then(users => new Map(users.map(u => [u.id, u]))),
this.chatRoomMembershipsRepository.find({ this.chatRoomMembershipsRepository.find({
@ -287,9 +290,15 @@ export class ChatEntityService {
userId: me.id, userId: me.id,
}, },
}).then(memberships => new Map(_rooms.map(r => [r.id, memberships.find(m => m.roomId === r.id)]))), }).then(memberships => new Map(_rooms.map(r => [r.id, memberships.find(m => m.roomId === r.id)]))),
this.chatRoomInvitationsRepository.find({
where: {
roomId: In(_rooms.map(x => x.id)),
userId: me.id,
},
}).then(invitations => new Map(_rooms.map(r => [r.id, invitations.find(i => i.roomId === r.id)]))),
]); ]);
return Promise.all(_rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners, memberships } }))); return Promise.all(_rooms.map(room => this.packRoom(room, me, { _hint_: { packedOwners, myMemberships, myInvitations } })));
} }
@bindThis @bindThis

View File

@ -36,5 +36,9 @@ export const packedChatRoomSchema = {
type: 'boolean', type: 'boolean',
optional: true, nullable: false, optional: true, nullable: false,
}, },
invitationExists: {
type: 'boolean',
optional: true, nullable: false,
},
}, },
} as const; } as const;

View File

@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</button> </button>
</div> </div>
</Transition> </Transition>
<XForm v-if="!initializing" :user="user" :room="room" :class="$style.form"/> <XForm v-if="initialized" :user="user" :room="room" :class="$style.form"/>
</div> </div>
</div> </div>
</template> </template>
@ -127,7 +127,8 @@ export type NormalizedChatMessage = Omit<Misskey.entities.ChatMessageLite, 'from
})[]; })[];
}; };
const initializing = ref(true); const initializing = ref(false);
const initialized = ref(false);
const moreFetching = ref(false); const moreFetching = ref(false);
const messages = ref<NormalizedChatMessage[]>([]); const messages = ref<NormalizedChatMessage[]>([]);
const canFetchMore = ref(false); const canFetchMore = ref(false);
@ -171,7 +172,10 @@ function normalizeMessage(message: Misskey.entities.ChatMessageLite | Misskey.en
async function initialize() { async function initialize() {
const LIMIT = 20; const LIMIT = 20;
if (initializing.value) return;
initializing.value = true; initializing.value = true;
initialized.value = false;
if (props.userId) { if (props.userId) {
const [u, m] = await Promise.all([ const [u, m] = await Promise.all([
@ -194,13 +198,44 @@ async function initialize() {
connection.value.on('react', onReact); connection.value.on('react', onReact);
connection.value.on('unreact', onUnreact); connection.value.on('unreact', onUnreact);
} else { } else {
const [r, m] = await Promise.all([ const [rResult, mResult] = await Promise.allSettled([
misskeyApi('chat/rooms/show', { roomId: props.roomId }), misskeyApi('chat/rooms/show', { roomId: props.roomId }),
misskeyApi('chat/messages/room-timeline', { roomId: props.roomId, limit: LIMIT }), misskeyApi('chat/messages/room-timeline', { roomId: props.roomId, limit: LIMIT }),
]); ]);
room.value = r as Misskey.entities.ChatRoomsShowResponse; if (rResult.status === 'rejected') {
messages.value = (m as Misskey.entities.ChatMessagesRoomTimelineResponse).map(x => normalizeMessage(x)); os.alert({
type: 'error',
text: i18n.ts.somethingHappened,
});
initializing.value = false;
return;
}
const r = rResult.value as Misskey.entities.ChatRoomsShowResponse;
if (r.invitationExists) {
const confirm = await os.confirm({
type: 'question',
title: r.name,
text: i18n.ts._chat.youAreNotAMemberOfThisRoomButInvited + '\n' + i18n.ts._chat.doYouAcceptInvitation,
});
if (confirm.canceled) {
initializing.value = false;
router.push('/chat');
return;
} else {
await os.apiWithDialog('chat/rooms/join', { roomId: r.id });
initializing.value = false;
initialize();
return;
}
}
const m = mResult.status === 'fulfilled' ? mResult.value as Misskey.entities.ChatMessagesRoomTimelineResponse : [];
room.value = r;
messages.value = m.map(x => normalizeMessage(x));
if (messages.value.length === LIMIT) { if (messages.value.length === LIMIT) {
canFetchMore.value = true; canFetchMore.value = true;
@ -217,6 +252,7 @@ async function initialize() {
window.document.addEventListener('visibilitychange', onVisibilitychange); window.document.addEventListener('visibilitychange', onVisibilitychange);
initialized.value = true;
initializing.value = false; initializing.value = false;
} }
@ -319,6 +355,12 @@ onMounted(() => {
initialize(); initialize();
}); });
onActivated(() => {
if (!initialized.value) {
initialize();
}
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
connection.value?.dispose(); connection.value?.dispose();
window.document.removeEventListener('visibilitychange', onVisibilitychange); window.document.removeEventListener('visibilitychange', onVisibilitychange);
@ -410,7 +452,7 @@ const headerActions = computed<PageHeaderItem[]>(() => [{
}]); }]);
definePage(computed(() => { definePage(computed(() => {
if (!initializing.value) { if (initialized.value) {
if (user.value) { if (user.value) {
return { return {
userName: user.value, userName: user.value,

View File

@ -5534,6 +5534,7 @@ export type components = {
name: string; name: string;
description: string; description: string;
isMuted?: boolean; isMuted?: boolean;
invitationExists?: boolean;
}; };
ChatRoomInvitation: { ChatRoomInvitation: {
id: string; id: string;