fix: チャット周りの修正 (#15741)
* fix(misskey-js): チャットのChannel型定義を追加 * fix(backend); canChatで塞いでいない書き込み系のAPIを塞ぐ * fix(frontend): チャット周りのフロントエンド型修正 * lint fix * fix broken lockfile * fix * refactor * wip * wip * wip * clean up --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
7cecaa5c54
commit
e07bb1dcbc
|
@ -99,7 +99,7 @@ export class ChatService {
|
||||||
text?: string | null;
|
text?: string | null;
|
||||||
file?: MiDriveFile | null;
|
file?: MiDriveFile | null;
|
||||||
uri?: string | null;
|
uri?: string | null;
|
||||||
}): Promise<Packed<'ChatMessageLite'>> {
|
}): Promise<Packed<'ChatMessageLiteFor1on1'>> {
|
||||||
if (fromUser.id === toUser.id) {
|
if (fromUser.id === toUser.id) {
|
||||||
throw new Error('yourself');
|
throw new Error('yourself');
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ export class ChatService {
|
||||||
text?: string | null;
|
text?: string | null;
|
||||||
file?: MiDriveFile | null;
|
file?: MiDriveFile | null;
|
||||||
uri?: string | null;
|
uri?: string | null;
|
||||||
}): Promise<Packed<'ChatMessageLite'>> {
|
}): Promise<Packed<'ChatMessageLiteForRoom'>> {
|
||||||
const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id })).map(m => ({
|
const memberships = (await this.chatRoomMembershipsRepository.findBy({ roomId: toRoom.id })).map(m => ({
|
||||||
userId: m.userId,
|
userId: m.userId,
|
||||||
isMuted: m.isMuted,
|
isMuted: m.isMuted,
|
||||||
|
|
|
@ -128,7 +128,7 @@ export class ChatEntityService {
|
||||||
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
packedFiles: Map<MiChatMessage['fileId'], Packed<'DriveFile'> | null>;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
): Promise<Packed<'ChatMessageLite'>> {
|
): Promise<Packed<'ChatMessageLiteFor1on1'>> {
|
||||||
const packedFiles = options?._hint_?.packedFiles;
|
const packedFiles = options?._hint_?.packedFiles;
|
||||||
|
|
||||||
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
const message = typeof src === 'object' ? src : await this.chatMessagesRepository.findOneByOrFail({ id: src });
|
||||||
|
@ -147,7 +147,7 @@ export class ChatEntityService {
|
||||||
createdAt: this.idService.parse(message.id).date.toISOString(),
|
createdAt: this.idService.parse(message.id).date.toISOString(),
|
||||||
text: message.text,
|
text: message.text,
|
||||||
fromUserId: message.fromUserId,
|
fromUserId: message.fromUserId,
|
||||||
toUserId: message.toUserId,
|
toUserId: message.toUserId!,
|
||||||
fileId: message.fileId,
|
fileId: message.fileId,
|
||||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||||
reactions,
|
reactions,
|
||||||
|
@ -177,7 +177,7 @@ export class ChatEntityService {
|
||||||
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
): Promise<Packed<'ChatMessageLite'>> {
|
): Promise<Packed<'ChatMessageLiteForRoom'>> {
|
||||||
const packedFiles = options?._hint_?.packedFiles;
|
const packedFiles = options?._hint_?.packedFiles;
|
||||||
const packedUsers = options?._hint_?.packedUsers;
|
const packedUsers = options?._hint_?.packedUsers;
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ export class ChatEntityService {
|
||||||
text: message.text,
|
text: message.text,
|
||||||
fromUserId: message.fromUserId,
|
fromUserId: message.fromUserId,
|
||||||
fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId),
|
fromUser: packedUsers?.get(message.fromUserId) ?? await this.userEntityService.pack(message.fromUser ?? message.fromUserId),
|
||||||
toRoomId: message.toRoomId,
|
toRoomId: message.toRoomId!,
|
||||||
fileId: message.fileId,
|
fileId: message.fileId,
|
||||||
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
file: message.fileId ? (packedFiles?.get(message.fileId) ?? await this.driveFileEntityService.pack(message.file ?? message.fileId)) : null,
|
||||||
reactions,
|
reactions,
|
||||||
|
|
|
@ -63,7 +63,7 @@ import {
|
||||||
} from '@/models/json-schema/meta.js';
|
} from '@/models/json-schema/meta.js';
|
||||||
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
||||||
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
|
import { packedAbuseReportNotificationRecipientSchema } from '@/models/json-schema/abuse-report-notification-recipient.js';
|
||||||
import { packedChatMessageSchema, packedChatMessageLiteSchema } from '@/models/json-schema/chat-message.js';
|
import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessageLiteForRoomSchema, packedChatMessageLiteFor1on1Schema } from '@/models/json-schema/chat-message.js';
|
||||||
import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js';
|
import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js';
|
||||||
import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js';
|
import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js';
|
||||||
import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
|
import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js';
|
||||||
|
@ -126,6 +126,8 @@ export const refs = {
|
||||||
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
||||||
ChatMessage: packedChatMessageSchema,
|
ChatMessage: packedChatMessageSchema,
|
||||||
ChatMessageLite: packedChatMessageLiteSchema,
|
ChatMessageLite: packedChatMessageLiteSchema,
|
||||||
|
ChatMessageLiteFor1on1: packedChatMessageLiteFor1on1Schema,
|
||||||
|
ChatMessageLiteForRoom: packedChatMessageLiteForRoomSchema,
|
||||||
ChatRoom: packedChatRoomSchema,
|
ChatRoom: packedChatRoomSchema,
|
||||||
ChatRoomInvitation: packedChatRoomInvitationSchema,
|
ChatRoomInvitation: packedChatRoomInvitationSchema,
|
||||||
ChatRoomMembership: packedChatRoomMembershipSchema,
|
ChatRoomMembership: packedChatRoomMembershipSchema,
|
||||||
|
|
|
@ -72,7 +72,7 @@ export const packedChatMessageSchema = {
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: true, nullable: true,
|
optional: false, nullable: false,
|
||||||
ref: 'UserLite',
|
ref: 'UserLite',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -144,3 +144,113 @@ export const packedChatMessageLiteSchema = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const packedChatMessageLiteFor1on1Schema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
fromUserId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
toUserId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
|
fileId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: 'object',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
ref: 'DriveFile',
|
||||||
|
},
|
||||||
|
reactions: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
reaction: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const packedChatMessageLiteForRoomSchema = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
fromUserId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
fromUser: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'UserLite',
|
||||||
|
},
|
||||||
|
toRoomId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
|
fileId: {
|
||||||
|
type: 'string',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: 'object',
|
||||||
|
optional: true, nullable: true,
|
||||||
|
ref: 'DriveFile',
|
||||||
|
},
|
||||||
|
reactions: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
reaction: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
ref: 'UserLite',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const meta = {
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'ChatMessageLite',
|
ref: 'ChatMessageLiteForRoom',
|
||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
|
|
|
@ -30,7 +30,7 @@ export const meta = {
|
||||||
res: {
|
res: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'ChatMessageLite',
|
ref: 'ChatMessageLiteFor1on1',
|
||||||
},
|
},
|
||||||
|
|
||||||
errors: {
|
errors: {
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const meta = {
|
||||||
tags: ['chat'],
|
tags: ['chat'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requiredRolePolicy: 'canChat',
|
||||||
|
|
||||||
kind: 'write:chat',
|
kind: 'write:chat',
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const meta = {
|
||||||
tags: ['chat'],
|
tags: ['chat'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requiredRolePolicy: 'canChat',
|
||||||
|
|
||||||
kind: 'write:chat',
|
kind: 'write:chat',
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const meta = {
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'ChatMessageLite',
|
ref: 'ChatMessageLiteForRoom',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const meta = {
|
||||||
tags: ['chat'],
|
tags: ['chat'],
|
||||||
|
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
|
requiredRolePolicy: 'canChat',
|
||||||
|
|
||||||
kind: 'write:chat',
|
kind: 'write:chat',
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const meta = {
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
ref: 'ChatMessageLite',
|
ref: 'ChatMessageLiteFor1on1',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[$style.root, { [$style.isMe]: isMe }]">
|
<div :class="[$style.root, { [$style.isMe]: isMe }]">
|
||||||
<MkAvatar :class="$style.avatar" :user="message.fromUser" :link="!isMe" :preview="false"/>
|
<MkAvatar :class="$style.avatar" :user="message.fromUser!" :link="!isMe" :preview="false"/>
|
||||||
<div :class="$style.body" @contextmenu.stop="onContextmenu">
|
<div :class="$style.body" @contextmenu.stop="onContextmenu">
|
||||||
<div :class="$style.header"><MkUserName v-if="!isMe && prefer.s['chat.showSenderName']" :user="message.fromUser"/></div>
|
<div :class="$style.header"><MkUserName v-if="!isMe && prefer.s['chat.showSenderName'] && message.fromUser != null" :user="message.fromUser"/></div>
|
||||||
<MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :accented="isMe">
|
<MkFukidashi :class="$style.fukidashi" :tail="isMe ? 'right' : 'left'" :accented="isMe">
|
||||||
<div v-if="!message.isDeleted" :class="$style.content">
|
|
||||||
<Mfm
|
<Mfm
|
||||||
v-if="message.text"
|
v-if="message.text"
|
||||||
ref="text"
|
ref="text"
|
||||||
|
@ -21,17 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:enableEmojiMenuReaction="true"
|
:enableEmojiMenuReaction="true"
|
||||||
/>
|
/>
|
||||||
<MkMediaList v-if="message.file" :mediaList="[message.file]" :class="$style.file"/>
|
<MkMediaList v-if="message.file" :mediaList="[message.file]" :class="$style.file"/>
|
||||||
</div>
|
|
||||||
<div v-else :class="$style.content">
|
|
||||||
<p>{{ i18n.ts.deleted }}</p>
|
|
||||||
</div>
|
|
||||||
</MkFukidashi>
|
</MkFukidashi>
|
||||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" style="margin: 8px 0;"/>
|
<MkUrlPreview v-for="url in urls" :key="url" :url="url" style="margin: 8px 0;"/>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<button class="_textButton" style="color: currentColor;" @click="showMenu"><i class="ti ti-dots-circle-horizontal"></i></button>
|
<button class="_textButton" style="color: currentColor;" @click="showMenu"><i class="ti ti-dots-circle-horizontal"></i></button>
|
||||||
<MkTime :class="$style.time" :time="message.createdAt"/>
|
<MkTime :class="$style.time" :time="message.createdAt"/>
|
||||||
<MkA v-if="isSearchResult && message.toRoomId" :to="`/chat/room/${message.toRoomId}`">{{ message.toRoom.name }}</MkA>
|
<MkA v-if="isSearchResult && 'toRoom' in message && message.toRoom != null" :to="`/chat/room/${message.toRoomId}`">{{ message.toRoom.name }}</MkA>
|
||||||
<MkA v-if="isSearchResult && message.toUserId && isMe" :to="`/chat/user/${message.toUserId}`">@{{ message.toUser.username }}</MkA>
|
<MkA v-if="isSearchResult && 'toUser' in message && message.toUser != null && isMe" :to="`/chat/user/${message.toUserId}`">@{{ message.toUser.username }}</MkA>
|
||||||
</div>
|
</div>
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
:enterActiveClass="prefer.s.animation ? $style.transition_reaction_enterActive : ''"
|
:enterActiveClass="prefer.s.animation ? $style.transition_reaction_enterActive : ''"
|
||||||
|
@ -62,6 +57,7 @@ import * as Misskey from 'misskey-js';
|
||||||
import { url } from '@@/js/config.js';
|
import { url } from '@@/js/config.js';
|
||||||
import { isLink } from '@@/js/is-link.js';
|
import { isLink } from '@@/js/is-link.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
import type { NormalizedChatMessage } from './room.vue';
|
||||||
import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
|
import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
|
@ -76,11 +72,12 @@ import * as sound from '@/utility/sound.js';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { DI } from '@/di.js';
|
import { DI } from '@/di.js';
|
||||||
|
import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
message: Misskey.entities.ChatMessageLite | Misskey.entities.ChatMessage;
|
message: NormalizedChatMessage | Misskey.entities.ChatMessage;
|
||||||
isSearchResult?: boolean;
|
isSearchResult?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -88,6 +85,8 @@ const isMe = computed(() => props.message.fromUserId === $i.id);
|
||||||
const urls = computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []);
|
const urls = computed(() => props.message.text ? extractUrlFromMfm(mfm.parse(props.message.text)) : []);
|
||||||
|
|
||||||
provide(DI.mfmEmojiReactCallback, (reaction) => {
|
provide(DI.mfmEmojiReactCallback, (reaction) => {
|
||||||
|
if (!$i.policies.canChat) return;
|
||||||
|
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
misskeyApi('chat/messages/react', {
|
misskeyApi('chat/messages/react', {
|
||||||
messageId: props.message.id,
|
messageId: props.message.id,
|
||||||
|
@ -96,7 +95,12 @@ provide(DI.mfmEmojiReactCallback, (reaction) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
function react(ev: MouseEvent) {
|
function react(ev: MouseEvent) {
|
||||||
reactionPicker.show(ev.currentTarget ?? ev.target, null, async (reaction) => {
|
if (!$i.policies.canChat) return;
|
||||||
|
|
||||||
|
const targetEl = getHTMLElementOrNull(ev.currentTarget ?? ev.target);
|
||||||
|
if (!targetEl) return;
|
||||||
|
|
||||||
|
reactionPicker.show(targetEl, null, async (reaction) => {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
misskeyApi('chat/messages/react', {
|
misskeyApi('chat/messages/react', {
|
||||||
messageId: props.message.id,
|
messageId: props.message.id,
|
||||||
|
@ -106,6 +110,8 @@ function react(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReactionClick(record: Misskey.entities.ChatMessage['reactions'][0]) {
|
function onReactionClick(record: Misskey.entities.ChatMessage['reactions'][0]) {
|
||||||
|
if (!$i.policies.canChat) return;
|
||||||
|
|
||||||
if (record.user.id === $i.id) {
|
if (record.user.id === $i.id) {
|
||||||
misskeyApi('chat/messages/unreact', {
|
misskeyApi('chat/messages/unreact', {
|
||||||
messageId: props.message.id,
|
messageId: props.message.id,
|
||||||
|
@ -132,7 +138,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||||
function showMenu(ev: MouseEvent, contextmenu = false) {
|
function showMenu(ev: MouseEvent, contextmenu = false) {
|
||||||
const menu: MenuItem[] = [];
|
const menu: MenuItem[] = [];
|
||||||
|
|
||||||
if (!isMe.value) {
|
if (!isMe.value && $i.policies.canChat) {
|
||||||
menu.push({
|
menu.push({
|
||||||
text: i18n.ts.reaction,
|
text: i18n.ts.reaction,
|
||||||
icon: 'ti ti-mood-plus',
|
icon: 'ti ti-mood-plus',
|
||||||
|
@ -150,7 +156,7 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
|
||||||
text: i18n.ts.copyContent,
|
text: i18n.ts.copyContent,
|
||||||
icon: 'ti ti-copy',
|
icon: 'ti ti-copy',
|
||||||
action: () => {
|
action: () => {
|
||||||
copyToClipboard(props.message.text);
|
copyToClipboard(props.message.text ?? '');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -158,7 +164,7 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isMe.value) {
|
if (isMe.value && $i.policies.canChat) {
|
||||||
menu.push({
|
menu.push({
|
||||||
text: i18n.ts.delete,
|
text: i18n.ts.delete,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
|
@ -169,14 +175,16 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!isMe.value && props.message.fromUser != null) {
|
||||||
menu.push({
|
menu.push({
|
||||||
text: i18n.ts.reportAbuse,
|
text: i18n.ts.reportAbuse,
|
||||||
icon: 'ti ti-exclamation-circle',
|
icon: 'ti ti-exclamation-circle',
|
||||||
action: () => {
|
action: () => {
|
||||||
const localUrl = `${url}/chat/messages/${props.message.id}`;
|
const localUrl = `${url}/chat/messages/${props.message.id}`;
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
|
||||||
user: props.message.fromUser,
|
user: props.message.fromUser!,
|
||||||
initialComment: `${localUrl}\n-----\n`,
|
initialComment: `${localUrl}\n-----\n`,
|
||||||
}, {
|
}, {
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
|
|
|
@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onActivated, onDeactivated, onMounted, ref } from 'vue';
|
import { onActivated, onDeactivated, onMounted, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { useInterval } from '@@/js/use-interval.js';
|
import { useInterval } from '@@/js/use-interval.js';
|
||||||
import XMessage from './XMessage.vue';
|
import XMessage from './XMessage.vue';
|
||||||
|
@ -163,7 +163,7 @@ async function fetchHistory() {
|
||||||
.map(m => ({
|
.map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
message: m,
|
message: m,
|
||||||
other: m.room == null ? (m.fromUserId === $i.id ? m.toUser : m.fromUser) : null,
|
other: (!('room' in m) || m.room == null) ? (m.fromUserId === $i.id ? m.toUser : m.fromUser) : null,
|
||||||
isMe: m.fromUserId === $i.id,
|
isMe: m.fromUserId === $i.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -35,18 +35,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
|
||||||
import { useRouter } from '@/router.js';
|
import { useRouter } from '@/router.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
|
@ -55,8 +51,7 @@ const invitations = ref<Misskey.entities.ChatRoomInvitation[]>([]);
|
||||||
async function fetchInvitations() {
|
async function fetchInvitations() {
|
||||||
fetching.value = true;
|
fetching.value = true;
|
||||||
|
|
||||||
const res = await misskeyApi('chat/rooms/invitations/inbox', {
|
const res = await misskeyApi('chat/rooms/invitations/inbox');
|
||||||
});
|
|
||||||
|
|
||||||
invitations.value = res;
|
invitations.value = res;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div v-if="memberships.length > 0" class="_gaps_s">
|
<div v-if="memberships.length > 0" class="_gaps_s">
|
||||||
<XRoom v-for="membership in memberships" :key="membership.id" :room="membership.room"/>
|
<XRoom v-for="membership in memberships" :key="membership.id" :room="membership.room!"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!fetching && memberships.length == 0" class="_fullinfo">
|
<div v-if="!fetching && memberships.length == 0" class="_fullinfo">
|
||||||
<div>{{ i18n.ts._chat.noRooms }}</div>
|
<div>{{ i18n.ts._chat.noRooms }}</div>
|
||||||
|
@ -16,19 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XRoom from './XRoom.vue';
|
import XRoom from './XRoom.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
|
||||||
import { useRouter } from '@/router.js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
const memberships = ref<Misskey.entities.ChatRoomMembership[]>([]);
|
const memberships = ref<Misskey.entities.ChatRoomMembership[]>([]);
|
||||||
|
@ -36,8 +28,7 @@ const memberships = ref<Misskey.entities.ChatRoomMembership[]>([]);
|
||||||
async function fetchRooms() {
|
async function fetchRooms() {
|
||||||
fetching.value = true;
|
fetching.value = true;
|
||||||
|
|
||||||
const res = await misskeyApi('chat/rooms/joining', {
|
const res = await misskeyApi('chat/rooms/joining');
|
||||||
});
|
|
||||||
|
|
||||||
memberships.value = res;
|
memberships.value = res;
|
||||||
|
|
||||||
|
|
|
@ -16,19 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XRoom from './XRoom.vue';
|
import XRoom from './XRoom.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
|
||||||
import { useRouter } from '@/router.js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
|
|
||||||
const $i = ensureSignin();
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
const rooms = ref<Misskey.entities.ChatRoom[]>([]);
|
const rooms = ref<Misskey.entities.ChatRoom[]>([]);
|
||||||
|
|
|
@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import XHome from './home.home.vue';
|
import XHome from './home.home.vue';
|
||||||
import XInvitations from './home.invitations.vue';
|
import XInvitations from './home.invitations.vue';
|
||||||
import XJoiningRooms from './home.joiningRooms.vue';
|
import XJoiningRooms from './home.joiningRooms.vue';
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<PageWithHeader>
|
<PageWithHeader>
|
||||||
<MkSpacer :contentMax="700">
|
<MkSpacer :contentMax="700">
|
||||||
<div v-if="initializing">
|
<div v-if="initializing || message == null">
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
@ -17,23 +17,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef, computed, watch, onMounted, nextTick, onBeforeUnmount, onDeactivated, onActivated } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XMessage from './XMessage.vue';
|
import XMessage from './XMessage.vue';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { useStream } from '@/stream.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import { definePage } from '@/page.js';
|
import { definePage } from '@/page.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const initializing = ref(true);
|
const initializing = ref(true);
|
||||||
const message = ref<Misskey.entities.ChatMessage>();
|
const message = ref<Misskey.entities.ChatMessage | null>();
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
initializing.value = true;
|
initializing.value = true;
|
||||||
|
|
|
@ -34,14 +34,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, watch, ref, shallowRef, computed, nextTick, readonly } from 'vue';
|
import { onMounted, watch, ref, shallowRef, computed, nextTick, readonly, onBeforeUnmount } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
//import insertTextAtCursor from 'insert-text-at-cursor';
|
//import insertTextAtCursor from 'insert-text-at-cursor';
|
||||||
import { throttle } from 'throttle-debounce';
|
|
||||||
import { formatTimeString } from '@/utility/format-time-string.js';
|
import { formatTimeString } from '@/utility/format-time-string.js';
|
||||||
import { selectFile } from '@/utility/select-file.js';
|
import { selectFile } from '@/utility/select-file.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { useStream } from '@/stream.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { uploadFile } from '@/utility/upload.js';
|
import { uploadFile } from '@/utility/upload.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
|
@ -62,6 +60,7 @@ const text = ref<string>('');
|
||||||
const file = ref<Misskey.entities.DriveFile | null>(null);
|
const file = ref<Misskey.entities.DriveFile | null>(null);
|
||||||
const sending = ref(false);
|
const sending = ref(false);
|
||||||
const textareaReadOnly = ref(false);
|
const textareaReadOnly = ref(false);
|
||||||
|
let autocompleteInstance: Autocomplete | null = null;
|
||||||
|
|
||||||
const canSend = computed(() => (text.value != null && text.value !== '') || file.value != null);
|
const canSend = computed(() => (text.value != null && text.value !== '') || file.value != null);
|
||||||
|
|
||||||
|
@ -171,7 +170,9 @@ function chooseFile(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeFile() {
|
function onChangeFile() {
|
||||||
if (fileEl.value.files![0]) upload(fileEl.value.files[0]);
|
if (fileEl.value == null || fileEl.value.files == null) return;
|
||||||
|
|
||||||
|
if (fileEl.value.files[0]) upload(fileEl.value.files[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function upload(fileToUpload: File, name?: string) {
|
function upload(fileToUpload: File, name?: string) {
|
||||||
|
@ -270,8 +271,9 @@ async function insertEmoji(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// TODO: detach when unmount
|
if (textareaEl.value != null) {
|
||||||
new Autocomplete(textareaEl.value, text);
|
autocompleteInstance = new Autocomplete(textareaEl.value, text);
|
||||||
|
}
|
||||||
|
|
||||||
// 書きかけの投稿を復元
|
// 書きかけの投稿を復元
|
||||||
const draft = JSON.parse(miLocalStorage.getItem('chatMessageDrafts') || '{}')[getDraftKey()];
|
const draft = JSON.parse(miLocalStorage.getItem('chatMessageDrafts') || '{}')[getDraftKey()];
|
||||||
|
@ -280,6 +282,13 @@ onMounted(() => {
|
||||||
file.value = draft.data.file;
|
file.value = draft.data.file;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (autocompleteInstance) {
|
||||||
|
autocompleteInstance.detach();
|
||||||
|
autocompleteInstance = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -26,11 +26,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -73,7 +72,7 @@ async function del() {
|
||||||
router.push('/chat');
|
router.push('/chat');
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMuted = ref(props.room.isMuted);
|
const isMuted = ref(props.room.isMuted ?? false);
|
||||||
|
|
||||||
watch(isMuted, async () => {
|
watch(isMuted, async () => {
|
||||||
await os.apiWithDialog('chat/rooms/mute', {
|
await os.apiWithDialog('chat/rooms/mute', {
|
||||||
|
|
|
@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<hr v-if="memberships.length > 0">
|
<hr v-if="memberships.length > 0">
|
||||||
|
|
||||||
<div v-for="membership in memberships" :key="membership.id" :class="$style.membership">
|
<div v-for="membership in memberships" :key="membership.id" :class="$style.membership">
|
||||||
<MkA :class="$style.membershipBody" :to="`${userPage(membership.user)}`">
|
<MkA :class="$style.membershipBody" :to="`${userPage(membership.user!)}`">
|
||||||
<MkUserCardMini :user="membership.user"/>
|
<MkUserCardMini :user="membership.user!"/>
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ import * as Misskey from 'misskey-js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
|
|
|
@ -33,14 +33,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XMessage from './XMessage.vue';
|
import XMessage from './XMessage.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { infoImageUrl } from '@/instance.js';
|
import { infoImageUrl } from '@/instance.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
import * as os from '@/os.js';
|
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
|
||||||
|
|
|
@ -79,15 +79,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, useTemplateRef, computed, watch, onMounted, nextTick, onBeforeUnmount, onDeactivated, onActivated } from 'vue';
|
import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, onDeactivated, onActivated } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { getScrollContainer, isTailVisible } from '@@/js/scroll.js';
|
import { getScrollContainer } from '@@/js/scroll.js';
|
||||||
import XMessage from './XMessage.vue';
|
import XMessage from './XMessage.vue';
|
||||||
import XForm from './room.form.vue';
|
import XForm from './room.form.vue';
|
||||||
import XSearch from './room.search.vue';
|
import XSearch from './room.search.vue';
|
||||||
import XMembers from './room.members.vue';
|
import XMembers from './room.members.vue';
|
||||||
import XInfo from './room.info.vue';
|
import XInfo from './room.info.vue';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import * as sound from '@/utility/sound.js';
|
import * as sound from '@/utility/sound.js';
|
||||||
|
@ -109,13 +110,20 @@ const props = defineProps<{
|
||||||
roomId?: string;
|
roomId?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
export type NormalizedChatMessage = Omit<Misskey.entities.ChatMessageLite, 'fromUser' | 'reactions'> & {
|
||||||
|
fromUser: Misskey.entities.UserLite;
|
||||||
|
reactions: (Misskey.entities.ChatMessageLite['reactions'][number] & {
|
||||||
|
user: Misskey.entities.UserLite;
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
|
||||||
const initializing = ref(true);
|
const initializing = ref(true);
|
||||||
const moreFetching = ref(false);
|
const moreFetching = ref(false);
|
||||||
const messages = ref<Misskey.entities.ChatMessage[]>([]);
|
const messages = ref<NormalizedChatMessage[]>([]);
|
||||||
const canFetchMore = ref(false);
|
const canFetchMore = ref(false);
|
||||||
const user = ref<Misskey.entities.UserDetailed | null>(null);
|
const user = ref<Misskey.entities.UserDetailed | null>(null);
|
||||||
const room = ref<Misskey.entities.ChatRoom | null>(null);
|
const room = ref<Misskey.entities.ChatRoom | null>(null);
|
||||||
const connection = ref<Misskey.ChannelConnection<Misskey.Channels['chatUser'] | Misskey.Channels['chatRoom']> | null>(null);
|
const connection = ref<Misskey.IChannelConnection<Misskey.Channels['chatUser']> | Misskey.IChannelConnection<Misskey.Channels['chatRoom']> | null>(null);
|
||||||
const showIndicator = ref(false);
|
const showIndicator = ref(false);
|
||||||
const timelineEl = useTemplateRef('timelineEl');
|
const timelineEl = useTemplateRef('timelineEl');
|
||||||
|
|
||||||
|
@ -138,18 +146,14 @@ useMutationObserver(timelineEl, {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function normalizeMessage(message: Misskey.entities.ChatMessageLite | Misskey.entities.ChatMessage) {
|
function normalizeMessage(message: Misskey.entities.ChatMessageLite | Misskey.entities.ChatMessage): NormalizedChatMessage {
|
||||||
const reactions = [...message.reactions];
|
|
||||||
for (const record of reactions) {
|
|
||||||
if (room.value == null && record.user == null) { // 1on1の時はuserは省略される
|
|
||||||
record.user = message.fromUserId === $i.id ? user.value : $i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...message,
|
...message,
|
||||||
fromUser: message.fromUser ?? (message.fromUserId === $i.id ? $i : user),
|
fromUser: message.fromUser ?? (message.fromUserId === $i.id ? $i : user.value!),
|
||||||
reactions,
|
reactions: message.reactions.map(record => ({
|
||||||
|
...record,
|
||||||
|
user: record.user ?? (message.fromUserId === $i.id ? user.value! : $i),
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,8 +188,8 @@ async function initialize() {
|
||||||
misskeyApi('chat/messages/room-timeline', { roomId: props.roomId, limit: LIMIT }),
|
misskeyApi('chat/messages/room-timeline', { roomId: props.roomId, limit: LIMIT }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
room.value = r;
|
room.value = r as Misskey.entities.ChatRoomsShowResponse;
|
||||||
messages.value = m.map(x => normalizeMessage(x));
|
messages.value = (m as Misskey.entities.ChatMessagesRoomTimelineResponse).map(x => normalizeMessage(x));
|
||||||
|
|
||||||
if (messages.value.length === LIMIT) {
|
if (messages.value.length === LIMIT) {
|
||||||
canFetchMore.value = true;
|
canFetchMore.value = true;
|
||||||
|
@ -221,11 +225,11 @@ async function fetchMore() {
|
||||||
moreFetching.value = true;
|
moreFetching.value = true;
|
||||||
|
|
||||||
const newMessages = props.userId ? await misskeyApi('chat/messages/user-timeline', {
|
const newMessages = props.userId ? await misskeyApi('chat/messages/user-timeline', {
|
||||||
userId: user.value.id,
|
userId: user.value!.id,
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
untilId: messages.value[messages.value.length - 1].id,
|
untilId: messages.value[messages.value.length - 1].id,
|
||||||
}) : await misskeyApi('chat/messages/room-timeline', {
|
}) : await misskeyApi('chat/messages/room-timeline', {
|
||||||
roomId: room.value.id,
|
roomId: room.value!.id,
|
||||||
limit: LIMIT,
|
limit: LIMIT,
|
||||||
untilId: messages.value[messages.value.length - 1].id,
|
untilId: messages.value[messages.value.length - 1].id,
|
||||||
});
|
});
|
||||||
|
@ -236,7 +240,7 @@ async function fetchMore() {
|
||||||
moreFetching.value = false;
|
moreFetching.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMessage(message: Misskey.entities.ChatMessage) {
|
function onMessage(message: Misskey.entities.ChatMessageLite) {
|
||||||
sound.playMisskeySfx('chatMessage');
|
sound.playMisskeySfx('chatMessage');
|
||||||
|
|
||||||
messages.value.unshift(normalizeMessage(message));
|
messages.value.unshift(normalizeMessage(message));
|
||||||
|
@ -253,34 +257,34 @@ function onMessage(message: Misskey.entities.ChatMessage) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onDeleted(id) {
|
function onDeleted(id: string) {
|
||||||
const index = messages.value.findIndex(m => m.id === id);
|
const index = messages.value.findIndex(m => m.id === id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
messages.value.splice(index, 1);
|
messages.value.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onReact(ctx) {
|
function onReact(ctx: Parameters<Misskey.Channels['chatUser']['events']['react']>[0] | Parameters<Misskey.Channels['chatRoom']['events']['react']>[0]) {
|
||||||
const message = messages.value.find(m => m.id === ctx.messageId);
|
const message = messages.value.find(m => m.id === ctx.messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
if (room.value == null) { // 1on1の時はuserは省略される
|
if (room.value == null) { // 1on1の時はuserは省略される
|
||||||
message.reactions.push({
|
message.reactions.push({
|
||||||
reaction: ctx.reaction,
|
reaction: ctx.reaction,
|
||||||
user: message.fromUserId === $i.id ? user : $i,
|
user: message.fromUserId === $i.id ? user.value! : $i,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
message.reactions.push({
|
message.reactions.push({
|
||||||
reaction: ctx.reaction,
|
reaction: ctx.reaction,
|
||||||
user: ctx.user,
|
user: ctx.user!,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUnreact(ctx) {
|
function onUnreact(ctx: Parameters<Misskey.Channels['chatUser']['events']['unreact']>[0] | Parameters<Misskey.Channels['chatRoom']['events']['unreact']>[0]) {
|
||||||
const message = messages.value.find(m => m.id === ctx.messageId);
|
const message = messages.value.find(m => m.id === ctx.messageId);
|
||||||
if (message) {
|
if (message) {
|
||||||
const index = message.reactions.findIndex(r => r.reaction === ctx.reaction && r.user.id === ctx.user.id);
|
const index = message.reactions.findIndex(r => r.reaction === ctx.reaction && r.user.id === ctx.user!.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
message.reactions.splice(index, 1);
|
message.reactions.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
@ -310,14 +314,18 @@ onBeforeUnmount(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
async function inviteUser() {
|
async function inviteUser() {
|
||||||
|
if (room.value == null) return;
|
||||||
|
|
||||||
const invitee = await os.selectUser({ includeSelf: false, localOnly: true });
|
const invitee = await os.selectUser({ includeSelf: false, localOnly: true });
|
||||||
os.apiWithDialog('chat/rooms/invitations/create', {
|
os.apiWithDialog('chat/rooms/invitations/create', {
|
||||||
roomId: room.value?.id,
|
roomId: room.value.id,
|
||||||
userId: invitee.id,
|
userId: invitee.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function leaveRoom() {
|
async function leaveRoom() {
|
||||||
|
if (room.value == null) return;
|
||||||
|
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
text: i18n.ts.areYouSure,
|
text: i18n.ts.areYouSure,
|
||||||
|
@ -325,7 +333,7 @@ async function leaveRoom() {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
misskeyApi('chat/rooms/leave', {
|
misskeyApi('chat/rooms/leave', {
|
||||||
roomId: room.value?.id,
|
roomId: room.value.id,
|
||||||
});
|
});
|
||||||
router.push('/chat');
|
router.push('/chat');
|
||||||
}
|
}
|
||||||
|
@ -384,19 +392,36 @@ const headerTabs = computed(() => room.value ? [{
|
||||||
icon: 'ti ti-search',
|
icon: 'ti ti-search',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
const headerActions = computed(() => [{
|
const headerActions = computed<PageHeaderItem[]>(() => [{
|
||||||
icon: 'ti ti-dots',
|
icon: 'ti ti-dots',
|
||||||
|
text: '',
|
||||||
handler: showMenu,
|
handler: showMenu,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
definePage(computed(() => !initializing.value ? user.value ? {
|
definePage(computed(() => {
|
||||||
userName: user,
|
if (!initializing.value) {
|
||||||
|
if (user.value) {
|
||||||
|
return {
|
||||||
|
userName: user.value,
|
||||||
title: user.value.name ?? user.value.username,
|
title: user.value.name ?? user.value.username,
|
||||||
avatar: user,
|
avatar: user.value,
|
||||||
} : {
|
};
|
||||||
title: room.value?.name,
|
} else if (room.value) {
|
||||||
|
return {
|
||||||
|
title: room.value.name,
|
||||||
icon: 'ti ti-users',
|
icon: 'ti ti-users',
|
||||||
} : null));
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
title: i18n.ts.chat,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
title: i18n.ts.chat,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { onUnmounted, watch } from 'vue';
|
import { onUnmounted, watch } from 'vue';
|
||||||
import type { Ref, ShallowRef } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
export function useMutationObserver(targetNodeRef: Ref<HTMLElement | undefined>, options: MutationObserverInit, callback: MutationCallback): void {
|
export function useMutationObserver(targetNodeRef: Ref<HTMLElement | null | undefined>, options: MutationObserverInit, callback: MutationCallback): void {
|
||||||
const observer = new MutationObserver(callback);
|
const observer = new MutationObserver(callback);
|
||||||
|
|
||||||
watch(targetNodeRef, (targetNode) => {
|
watch(targetNodeRef, (targetNode) => {
|
||||||
|
|
|
@ -813,6 +813,54 @@ export type Channels = {
|
||||||
claimTimeIsUp: null | Record<string, never>;
|
claimTimeIsUp: null | Record<string, never>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
chatUser: {
|
||||||
|
params: {
|
||||||
|
otherId: string;
|
||||||
|
};
|
||||||
|
events: {
|
||||||
|
message: (payload: ChatMessageLite) => void;
|
||||||
|
deleted: (payload: ChatMessageLite['id']) => void;
|
||||||
|
react: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
unreact: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
receives: {
|
||||||
|
read: {
|
||||||
|
id: ChatMessageLite['id'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
chatRoom: {
|
||||||
|
params: {
|
||||||
|
roomId: string;
|
||||||
|
};
|
||||||
|
events: {
|
||||||
|
message: (payload: ChatMessageLite) => void;
|
||||||
|
deleted: (payload: ChatMessageLite['id']) => void;
|
||||||
|
react: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
unreact: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
receives: {
|
||||||
|
read: {
|
||||||
|
id: ChatMessageLite['id'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -959,6 +1007,12 @@ type ChatMessage = components['schemas']['ChatMessage'];
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type ChatMessageLite = components['schemas']['ChatMessageLite'];
|
type ChatMessageLite = components['schemas']['ChatMessageLite'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type ChatMessageLiteFor1on1 = components['schemas']['ChatMessageLiteFor1on1'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type ChatMessageLiteForRoom = components['schemas']['ChatMessageLiteForRoom'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
|
type ChatMessagesCreateToRoomRequest = operations['chat___messages___create-to-room']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
@ -2086,6 +2140,8 @@ declare namespace entities {
|
||||||
AbuseReportNotificationRecipient,
|
AbuseReportNotificationRecipient,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
ChatMessageLite,
|
ChatMessageLite,
|
||||||
|
ChatMessageLiteFor1on1,
|
||||||
|
ChatMessageLiteForRoom,
|
||||||
ChatRoom,
|
ChatRoom,
|
||||||
ChatRoomInvitation,
|
ChatRoomInvitation,
|
||||||
ChatRoomMembership
|
ChatRoomMembership
|
||||||
|
@ -3655,8 +3711,8 @@ type V2AdminEmojiListResponse = operations['v2___admin___emoji___list']['respons
|
||||||
//
|
//
|
||||||
// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
|
// src/streaming.ts:57:3 - (ae-forgotten-export) The symbol "ReconnectingWebSocket" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:217:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:218:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
|
||||||
// src/streaming.types.ts:227:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
|
// src/streaming.types.ts:228:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
|
||||||
|
|
||||||
// (No @packageDocumentation comment for this package)
|
// (No @packageDocumentation comment for this package)
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,8 @@ export type SystemWebhook = components['schemas']['SystemWebhook'];
|
||||||
export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient'];
|
export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient'];
|
||||||
export type ChatMessage = components['schemas']['ChatMessage'];
|
export type ChatMessage = components['schemas']['ChatMessage'];
|
||||||
export type ChatMessageLite = components['schemas']['ChatMessageLite'];
|
export type ChatMessageLite = components['schemas']['ChatMessageLite'];
|
||||||
|
export type ChatMessageLiteFor1on1 = components['schemas']['ChatMessageLiteFor1on1'];
|
||||||
|
export type ChatMessageLiteForRoom = components['schemas']['ChatMessageLiteForRoom'];
|
||||||
export type ChatRoom = components['schemas']['ChatRoom'];
|
export type ChatRoom = components['schemas']['ChatRoom'];
|
||||||
export type ChatRoomInvitation = components['schemas']['ChatRoomInvitation'];
|
export type ChatRoomInvitation = components['schemas']['ChatRoomInvitation'];
|
||||||
export type ChatRoomMembership = components['schemas']['ChatRoomMembership'];
|
export type ChatRoomMembership = components['schemas']['ChatRoomMembership'];
|
||||||
|
|
|
@ -5406,10 +5406,10 @@ export type components = {
|
||||||
fileId?: string | null;
|
fileId?: string | null;
|
||||||
file?: components['schemas']['DriveFile'] | null;
|
file?: components['schemas']['DriveFile'] | null;
|
||||||
isRead?: boolean;
|
isRead?: boolean;
|
||||||
reactions: ({
|
reactions: {
|
||||||
reaction: string;
|
reaction: string;
|
||||||
user?: components['schemas']['UserLite'] | null;
|
user: components['schemas']['UserLite'];
|
||||||
})[];
|
}[];
|
||||||
};
|
};
|
||||||
ChatMessageLite: {
|
ChatMessageLite: {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -5427,6 +5427,34 @@ export type components = {
|
||||||
user?: components['schemas']['UserLite'] | null;
|
user?: components['schemas']['UserLite'] | null;
|
||||||
})[];
|
})[];
|
||||||
};
|
};
|
||||||
|
ChatMessageLiteFor1on1: {
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
fromUserId: string;
|
||||||
|
toUserId: string;
|
||||||
|
text?: string | null;
|
||||||
|
fileId?: string | null;
|
||||||
|
file?: components['schemas']['DriveFile'] | null;
|
||||||
|
reactions: {
|
||||||
|
reaction: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
ChatMessageLiteForRoom: {
|
||||||
|
id: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
createdAt: string;
|
||||||
|
fromUserId: string;
|
||||||
|
fromUser: components['schemas']['UserLite'];
|
||||||
|
toRoomId: string;
|
||||||
|
text?: string | null;
|
||||||
|
fileId?: string | null;
|
||||||
|
file?: components['schemas']['DriveFile'] | null;
|
||||||
|
reactions: {
|
||||||
|
reaction: string;
|
||||||
|
user: components['schemas']['UserLite'];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
ChatRoom: {
|
ChatRoom: {
|
||||||
id: string;
|
id: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
|
@ -14067,7 +14095,7 @@ export type operations = {
|
||||||
/** @description OK (with results) */
|
/** @description OK (with results) */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': components['schemas']['ChatMessageLite'];
|
'application/json': components['schemas']['ChatMessageLiteForRoom'];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
@ -14130,7 +14158,7 @@ export type operations = {
|
||||||
/** @description OK (with results) */
|
/** @description OK (with results) */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': components['schemas']['ChatMessageLite'];
|
'application/json': components['schemas']['ChatMessageLiteFor1on1'];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
@ -14305,7 +14333,7 @@ export type operations = {
|
||||||
/** @description OK (with results) */
|
/** @description OK (with results) */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': components['schemas']['ChatMessageLite'][];
|
'application/json': components['schemas']['ChatMessageLiteForRoom'][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
@ -14533,7 +14561,7 @@ export type operations = {
|
||||||
/** @description OK (with results) */
|
/** @description OK (with results) */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
'application/json': components['schemas']['ChatMessageLite'][];
|
'application/json': components['schemas']['ChatMessageLiteFor1on1'][];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Client error */
|
/** @description Client error */
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
Antenna,
|
Antenna,
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
|
ChatMessageLite,
|
||||||
DriveFile,
|
DriveFile,
|
||||||
DriveFolder,
|
DriveFolder,
|
||||||
Note,
|
Note,
|
||||||
|
@ -227,7 +228,55 @@ export type Channels = {
|
||||||
updateSettings: ReversiUpdateSettings<ReversiUpdateKey>;
|
updateSettings: ReversiUpdateSettings<ReversiUpdateKey>;
|
||||||
claimTimeIsUp: null | Record<string, never>;
|
claimTimeIsUp: null | Record<string, never>;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
chatUser: {
|
||||||
|
params: {
|
||||||
|
otherId: string;
|
||||||
|
};
|
||||||
|
events: {
|
||||||
|
message: (payload: ChatMessageLite) => void;
|
||||||
|
deleted: (payload: ChatMessageLite['id']) => void;
|
||||||
|
react: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
unreact: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
receives: {
|
||||||
|
read: {
|
||||||
|
id: ChatMessageLite['id'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
chatRoom: {
|
||||||
|
params: {
|
||||||
|
roomId: string;
|
||||||
|
};
|
||||||
|
events: {
|
||||||
|
message: (payload: ChatMessageLite) => void;
|
||||||
|
deleted: (payload: ChatMessageLite['id']) => void;
|
||||||
|
react: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
unreact: (payload: {
|
||||||
|
reaction: string;
|
||||||
|
user?: UserLite;
|
||||||
|
messageId: ChatMessageLite['id'];
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
receives: {
|
||||||
|
read: {
|
||||||
|
id: ChatMessageLite['id'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NoteUpdatedEvent = { id: Note['id'] } & ({
|
export type NoteUpdatedEvent = { id: Note['id'] } & ({
|
||||||
|
|
|
@ -4564,8 +4564,8 @@ packages:
|
||||||
'@types/pg@8.6.1':
|
'@types/pg@8.6.1':
|
||||||
resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==}
|
resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==}
|
||||||
|
|
||||||
'@types/prop-types@15.7.5':
|
'@types/prop-types@15.7.14':
|
||||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
|
||||||
|
|
||||||
'@types/pug@2.0.10':
|
'@types/pug@2.0.10':
|
||||||
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
|
||||||
|
@ -4603,8 +4603,8 @@ packages:
|
||||||
'@types/sanitize-html@2.13.0':
|
'@types/sanitize-html@2.13.0':
|
||||||
resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
|
resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
|
||||||
|
|
||||||
'@types/scheduler@0.16.2':
|
'@types/scheduler@0.23.0':
|
||||||
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
|
resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==}
|
||||||
|
|
||||||
'@types/seedrandom@2.4.34':
|
'@types/seedrandom@2.4.34':
|
||||||
resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==}
|
resolution: {integrity: sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==}
|
||||||
|
@ -8479,6 +8479,10 @@ packages:
|
||||||
resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
|
resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
node-abi@3.74.0:
|
||||||
|
resolution: {integrity: sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
node-abort-controller@3.1.1:
|
node-abort-controller@3.1.1:
|
||||||
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
|
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
|
||||||
|
|
||||||
|
@ -9350,6 +9354,9 @@ packages:
|
||||||
pump@3.0.0:
|
pump@3.0.0:
|
||||||
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
|
||||||
|
|
||||||
|
pump@3.0.2:
|
||||||
|
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
|
||||||
|
|
||||||
punycode.js@2.3.1:
|
punycode.js@2.3.1:
|
||||||
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
@ -9473,6 +9480,10 @@ packages:
|
||||||
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
|
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
readable-stream@3.6.2:
|
||||||
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
readable-stream@4.3.0:
|
readable-stream@4.3.0:
|
||||||
resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==}
|
resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==}
|
||||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
@ -9738,6 +9749,11 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
semver@7.7.1:
|
||||||
|
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
send@0.19.0:
|
send@0.19.0:
|
||||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
@ -14515,7 +14531,7 @@ snapshots:
|
||||||
|
|
||||||
'@stylistic/eslint-plugin@2.13.0(eslint@9.22.0)(typescript@5.8.2)':
|
'@stylistic/eslint-plugin@2.13.0(eslint@9.22.0)(typescript@5.8.2)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/utils': 8.27.0(eslint@9.22.0)(typescript@5.8.2)
|
'@typescript-eslint/utils': 8.29.0(eslint@9.22.0)(typescript@5.8.2)
|
||||||
eslint: 9.22.0
|
eslint: 9.22.0
|
||||||
eslint-visitor-keys: 4.2.0
|
eslint-visitor-keys: 4.2.0
|
||||||
espree: 10.3.0
|
espree: 10.3.0
|
||||||
|
@ -14991,7 +15007,7 @@ snapshots:
|
||||||
pg-protocol: 1.7.1
|
pg-protocol: 1.7.1
|
||||||
pg-types: 2.2.0
|
pg-types: 2.2.0
|
||||||
|
|
||||||
'@types/prop-types@15.7.5': {}
|
'@types/prop-types@15.7.14': {}
|
||||||
|
|
||||||
'@types/pug@2.0.10': {}
|
'@types/pug@2.0.10': {}
|
||||||
|
|
||||||
|
@ -15011,8 +15027,8 @@ snapshots:
|
||||||
|
|
||||||
'@types/react@18.0.28':
|
'@types/react@18.0.28':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.14
|
||||||
'@types/scheduler': 0.16.2
|
'@types/scheduler': 0.23.0
|
||||||
csstype: 3.1.3
|
csstype: 3.1.3
|
||||||
|
|
||||||
'@types/readdir-glob@1.1.1':
|
'@types/readdir-glob@1.1.1':
|
||||||
|
@ -15027,7 +15043,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
htmlparser2: 8.0.1
|
htmlparser2: 8.0.1
|
||||||
|
|
||||||
'@types/scheduler@0.16.2': {}
|
'@types/scheduler@0.23.0': {}
|
||||||
|
|
||||||
'@types/seedrandom@2.4.34': {}
|
'@types/seedrandom@2.4.34': {}
|
||||||
|
|
||||||
|
@ -16071,7 +16087,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer: 5.7.1
|
buffer: 5.7.1
|
||||||
inherits: 2.0.4
|
inherits: 2.0.4
|
||||||
readable-stream: 3.6.0
|
readable-stream: 3.6.2
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
blob-util@2.0.2: {}
|
blob-util@2.0.2: {}
|
||||||
|
@ -20143,6 +20159,11 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.6.3
|
semver: 7.6.3
|
||||||
|
|
||||||
|
node-abi@3.74.0:
|
||||||
|
dependencies:
|
||||||
|
semver: 7.7.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
node-abort-controller@3.1.1: {}
|
node-abort-controller@3.1.1: {}
|
||||||
|
|
||||||
node-addon-api@3.2.1:
|
node-addon-api@3.2.1:
|
||||||
|
@ -20841,8 +20862,8 @@ snapshots:
|
||||||
minimist: 1.2.8
|
minimist: 1.2.8
|
||||||
mkdirp-classic: 0.5.3
|
mkdirp-classic: 0.5.3
|
||||||
napi-build-utils: 2.0.0
|
napi-build-utils: 2.0.0
|
||||||
node-abi: 3.62.0
|
node-abi: 3.74.0
|
||||||
pump: 3.0.0
|
pump: 3.0.2
|
||||||
rc: 1.2.8
|
rc: 1.2.8
|
||||||
simple-get: 4.0.1
|
simple-get: 4.0.1
|
||||||
tar-fs: 2.1.2
|
tar-fs: 2.1.2
|
||||||
|
@ -21020,6 +21041,12 @@ snapshots:
|
||||||
end-of-stream: 1.4.4
|
end-of-stream: 1.4.4
|
||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
|
|
||||||
|
pump@3.0.2:
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.4
|
||||||
|
once: 1.4.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
punycode.js@2.3.1: {}
|
punycode.js@2.3.1: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
@ -21161,6 +21188,13 @@ snapshots:
|
||||||
string_decoder: 1.3.0
|
string_decoder: 1.3.0
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
readable-stream@3.6.2:
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
readable-stream@4.3.0:
|
readable-stream@4.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
abort-controller: 3.0.0
|
abort-controller: 3.0.0
|
||||||
|
@ -21460,6 +21494,9 @@ snapshots:
|
||||||
|
|
||||||
semver@7.6.3: {}
|
semver@7.6.3: {}
|
||||||
|
|
||||||
|
semver@7.7.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
send@0.19.0:
|
send@0.19.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 2.6.9
|
debug: 2.6.9
|
||||||
|
@ -22036,7 +22073,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
chownr: 1.1.4
|
chownr: 1.1.4
|
||||||
mkdirp-classic: 0.5.3
|
mkdirp-classic: 0.5.3
|
||||||
pump: 3.0.0
|
pump: 3.0.2
|
||||||
tar-stream: 2.2.0
|
tar-stream: 2.2.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -22046,7 +22083,7 @@ snapshots:
|
||||||
end-of-stream: 1.4.4
|
end-of-stream: 1.4.4
|
||||||
fs-constants: 1.0.0
|
fs-constants: 1.0.0
|
||||||
inherits: 2.0.4
|
inherits: 2.0.4
|
||||||
readable-stream: 3.6.0
|
readable-stream: 3.6.2
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
tar-stream@3.1.6:
|
tar-stream@3.1.6:
|
||||||
|
|
Loading…
Reference in New Issue