diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts index 46ec13704c..54ce4d472a 100644 --- a/packages/backend/src/core/entities/NoteReactionEntityService.ts +++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts @@ -49,15 +49,12 @@ export class NoteReactionEntityService implements OnModuleInit { public async pack( src: MiNoteReaction['id'] | MiNoteReaction, me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, hints?: { packedUser?: Packed<'UserLite'> }, ): Promise> { const opts = Object.assign({ - withNote: false, }, options); const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); @@ -67,9 +64,6 @@ export class NoteReactionEntityService implements OnModuleInit { createdAt: this.idService.parse(reaction.id).date.toISOString(), user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), type: this.reactionService.convertLegacyReaction(reaction.reaction), - ...(opts.withNote ? { - note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), - } : {}), }; } @@ -77,16 +71,50 @@ export class NoteReactionEntityService implements OnModuleInit { public async packMany( reactions: MiNoteReaction[], me?: { id: MiUser['id'] } | null | undefined, - options?: { - withNote: boolean; - }, + options?: object, ): Promise[]> { const opts = Object.assign({ - withNote: false, }, options); const _users = reactions.map(({ user, userId }) => user ?? userId); const _userMap = await this.userEntityService.packMany(_users, me) .then(users => new Map(users.map(u => [u.id, u]))); return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); } + + @bindThis + public async packWithNote( + src: MiNoteReaction['id'] | MiNoteReaction, + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + hints?: { + packedUser?: Packed<'UserLite'> + }, + ): Promise> { + const opts = Object.assign({ + }, options); + + const reaction = typeof src === 'object' ? src : await this.noteReactionsRepository.findOneByOrFail({ id: src }); + + return { + id: reaction.id, + createdAt: this.idService.parse(reaction.id).date.toISOString(), + user: hints?.packedUser ?? await this.userEntityService.pack(reaction.user ?? reaction.userId, me), + type: this.reactionService.convertLegacyReaction(reaction.reaction), + note: await this.noteEntityService.pack(reaction.note ?? reaction.noteId, me), + }; + } + + @bindThis + public async packManyWithNote( + reactions: MiNoteReaction[], + me?: { id: MiUser['id'] } | null | undefined, + options?: object, + ): Promise[]> { + const opts = Object.assign({ + }, options); + const _users = reactions.map(({ user, userId }) => user ?? userId); + const _userMap = await this.userEntityService.packMany(_users, me) + .then(users => new Map(users.map(u => [u.id, u]))); + return Promise.all(reactions.map(reaction => this.packWithNote(reaction, me, opts, { packedUser: _userMap.get(reaction.userId) }))); + } } diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index ed47edff9b..dca92e1037 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -22,7 +22,7 @@ import { packedFollowingSchema } from '@/models/json-schema/following.js'; import { packedMutingSchema } from '@/models/json-schema/muting.js'; import { packedRenoteMutingSchema } from '@/models/json-schema/renote-muting.js'; import { packedBlockingSchema } from '@/models/json-schema/blocking.js'; -import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js'; +import { packedNoteReactionSchema, packedNoteReactionWithNoteSchema } from '@/models/json-schema/note-reaction.js'; import { packedHashtagSchema } from '@/models/json-schema/hashtag.js'; import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js'; import { packedPageBlockSchema, packedPageSchema } from '@/models/json-schema/page.js'; @@ -92,6 +92,7 @@ export const refs = { Note: packedNoteSchema, NoteDraft: packedNoteDraftSchema, NoteReaction: packedNoteReactionSchema, + NoteReactionWithNote: packedNoteReactionWithNoteSchema, NoteFavorite: packedNoteFavoriteSchema, Notification: packedNotificationSchema, DriveFile: packedDriveFileSchema, diff --git a/packages/backend/src/models/json-schema/note-reaction.ts b/packages/backend/src/models/json-schema/note-reaction.ts index 95658ace1f..04c9f34232 100644 --- a/packages/backend/src/models/json-schema/note-reaction.ts +++ b/packages/backend/src/models/json-schema/note-reaction.ts @@ -10,7 +10,6 @@ export const packedNoteReactionSchema = { type: 'string', optional: false, nullable: false, format: 'id', - example: 'xxxxxxxxxx', }, createdAt: { type: 'string', @@ -28,3 +27,33 @@ export const packedNoteReactionSchema = { }, }, } as const; + +export const packedNoteReactionWithNoteSchema = { + type: 'object', + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + user: { + type: 'object', + optional: false, nullable: false, + ref: 'UserLite', + }, + type: { + type: 'string', + optional: false, nullable: false, + }, + note: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', + }, + }, +} as const; diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 81a788de2b..804bd5d9b9 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -49,6 +49,34 @@ export const meta = { type: 'string', optional: false, nullable: false, }, + icon: { + type: 'string', + optional: false, nullable: true, + }, + display: { + type: 'string', + optional: false, nullable: false, + }, + isActive: { + type: 'boolean', + optional: false, nullable: false, + }, + forExistingUsers: { + type: 'boolean', + optional: false, nullable: false, + }, + silence: { + type: 'boolean', + optional: false, nullable: false, + }, + needConfirmationToRead: { + type: 'boolean', + optional: false, nullable: false, + }, + userId: { + type: 'string', + optional: false, nullable: true, + }, imageUrl: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8756801fe4..ed5952d4c5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -23,6 +23,16 @@ export const meta = { type: 'object', optional: false, nullable: false, ref: 'UserList', + properties: { + likedCount: { + type: 'number', + optional: true, nullable: false, + }, + isLiked: { + type: 'boolean', + optional: true, nullable: false, + }, + }, }, errors: { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index d6f1ecd8ed..d84a191f7a 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -28,7 +28,7 @@ export const meta = { items: { type: 'object', optional: false, nullable: false, - ref: 'NoteReaction', + ref: 'NoteReactionWithNote', }, }, @@ -120,7 +120,7 @@ export default class extends Endpoint { // eslint- return true; }); - return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true }); + return await this.noteReactionEntityService.packManyWithNote(reactions, me); }); } } diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 6ae8379801..18817d3f79 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -368,11 +368,6 @@ export async function mainBoot() { }); }); - main.on('unreadAntenna', () => { - updateCurrentAccountPartial({ hasUnreadAntenna: true }); - sound.playMisskeySfx('antenna'); - }); - main.on('newChatMessage', () => { updateCurrentAccountPartial({ hasUnreadChatMessages: true }); sound.playMisskeySfx('chatMessage'); diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index 3b7b59b4d3..bf39c1e983 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum', }]" > -
+
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 4d67bba70d..c54081ad42 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -589,7 +589,10 @@ const fetchDriveFilesChart = async (): Promise => { }; const fetchInstanceRequestsChart = async (): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'In', @@ -611,7 +614,10 @@ const fetchInstanceRequestsChart = async (): Promise => { }; const fetchInstanceUsersChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Users', @@ -626,7 +632,10 @@ const fetchInstanceUsersChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Notes', @@ -641,7 +650,10 @@ const fetchInstanceNotesChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Following', @@ -664,7 +676,10 @@ const fetchInstanceFfChart = async (total: boolean): Promise = }; const fetchInstanceDriveUsageChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { bytes: true, series: [{ @@ -680,7 +695,10 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span }); + const host = props.args?.host; + if (host == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/instance', { host: host, limit: props.limit, span: props.span }); return { series: [{ name: 'Drive files', @@ -695,7 +713,10 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise => { - const raw = await misskeyApiGet('charts/user/notes', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/notes', { userId: userId, limit: props.limit, span: props.span }); return { series: [...(props.args?.withoutAll ? [] : [{ name: 'All', @@ -727,7 +748,10 @@ const fetchPerUserNotesChart = async (): Promise => { }; const fetchPerUserPvChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/pv', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/pv', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Unique PV (user)', @@ -754,7 +778,10 @@ const fetchPerUserPvChart = async (): Promise => { }; const fetchPerUserFollowingChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -769,7 +796,10 @@ const fetchPerUserFollowingChart = async (): Promise => { }; const fetchPerUserFollowersChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/following', { userId: userId, limit: props.limit, span: props.span }); return { series: [{ name: 'Local', @@ -784,7 +814,10 @@ const fetchPerUserFollowersChart = async (): Promise => { }; const fetchPerUserDriveChart = async (): Promise => { - const raw = await misskeyApiGet('charts/user/drive', { userId: props.args?.user?.id, limit: props.limit, span: props.span }); + const userId = props.args?.user?.id; + if (userId == null) return { series: [] }; + + const raw = await misskeyApiGet('charts/user/drive', { userId: userId, limit: props.limit, span: props.span }); return { bytes: true, series: [{ diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 7f592fba79..6c07eac47a 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only