diff --git a/locales/index.d.ts b/locales/index.d.ts index 846a6d503d..da976aca93 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -682,6 +682,7 @@ export interface Locale { "forwardReport": string; "forwardReportIsAnonymous": string; "send": string; + "reportedNote": string; "abuseMarkAsResolved": string; "openInNewTab": string; "openInSideView": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0d84440bc8..9c26c03397 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -679,6 +679,7 @@ reporterOrigin: "通報元" forwardReport: "リモートサーバーに通報を転送する" forwardReportIsAnonymous: "リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。" send: "送信" +reportedNote: "通報されたノート" abuseMarkAsResolved: "対応済みにする" openInNewTab: "新しいタブで開く" openInSideView: "サイドビューで開く" diff --git a/packages/backend/migration/1702149469508-abusenoteselect.js b/packages/backend/migration/1702149469508-abusenoteselect.js new file mode 100644 index 0000000000..eb9d5b34d8 --- /dev/null +++ b/packages/backend/migration/1702149469508-abusenoteselect.js @@ -0,0 +1,11 @@ +export class Abusenoteselect1702149469508 { + name = 'Abusenoteselect1702149469508' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "notes" jsonb NOT NULL DEFAULT '[]'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "notes"`); + } +} diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts index d175f21f2f..f16c18c2d6 100644 --- a/packages/backend/src/core/GlobalEventService.ts +++ b/packages/backend/src/core/GlobalEventService.ts @@ -157,6 +157,7 @@ export interface AdminEventTypes { targetUserId: MiUser['id'], reporterId: MiUser['id'], comment: string; + notes: any[]; }; } //#endregion diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index 97de891ece..627dddd3bc 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -33,6 +33,7 @@ export class AbuseUserReportEntityService { id: report.id, createdAt: this.idService.parse(report.id).date.toISOString(), comment: report.comment, + notes: report.notes, resolved: report.resolved, reporterId: report.reporterId, targetUserId: report.targetUserId, diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index 593c44f66b..995fbc4018 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -60,6 +60,11 @@ export class MiAbuseUserReport { }) public comment: string; + @Column('jsonb', { + default: [], + }) + public notes: any[]; + //#region Denormalized fields @Index() @Column('varchar', { diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 3bcf44cc42..d54b01a6f1 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -3,14 +3,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { setImmediate } from 'node:timers/promises'; import sanitizeHtml from 'sanitize-html'; import { Inject, Injectable } from '@nestjs/common'; -import type { AbuseUserReportsRepository } from '@/models/_.js'; +import { In } from 'typeorm'; +import type { AbuseUserReportsRepository, NotesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { MetaService } from '@/core/MetaService.js'; import { EmailService } from '@/core/EmailService.js'; +import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { GetterService } from '@/server/api/GetterService.js'; import { RoleService } from '@/core/RoleService.js'; @@ -21,7 +24,7 @@ export const meta = { requireCredential: true, - description: 'File a report.', + description: 'User a report.', errors: { noSuchUser: { @@ -49,6 +52,7 @@ export const paramDef = { properties: { userId: { type: 'string', format: 'misskey:id' }, comment: { type: 'string', minLength: 1, maxLength: 2048 }, + noteIds: { type: 'array', items: { type: 'string', format: 'misskey:id' } }, }, required: ['userId', 'comment'], } as const; @@ -59,11 +63,15 @@ export default class extends Endpoint { // eslint- @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + private idService: IdService, private metaService: MetaService, private emailService: EmailService, private getterService: GetterService, private roleService: RoleService, + private noteEntityService: NoteEntityService, private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { @@ -81,6 +89,10 @@ export default class extends Endpoint { // eslint- throw new ApiError(meta.errors.cannotReportAdmin); } + const notes = ps.noteIds ? await this.notesRepository.find({ + where: { id: In(ps.noteIds) }, + }) : []; + const filteredNotes = notes.filter(note => note.userId === user.id); const report = await this.abuseUserReportsRepository.insert({ id: this.idService.gen(), targetUserId: user.id, @@ -88,6 +100,7 @@ export default class extends Endpoint { // eslint- reporterId: me.id, reporterHost: null, comment: ps.comment, + notes: ps.noteIds ? await this.noteEntityService.packMany(filteredNotes) : [], }).then(x => this.abuseUserReportsRepository.findOneByOrFail(x.identifiers[0])); // Publish event to moderators @@ -100,9 +113,9 @@ export default class extends Endpoint { // eslint- targetUserId: report.targetUserId, reporterId: report.reporterId, comment: report.comment, + notes: report.notes, }); } - const meta = await this.metaService.fetch(); if (meta.email) { this.emailService.sendEmail(meta.email, 'New abuse report', diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index ce7e134b70..4c8a334b23 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -4,13 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -37,23 +63,46 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; +import MkPagination from '@/components/MkPagination.vue'; +import MkNoteSimple from '@/components/MkNoteSimple.vue'; +import MkSwitch from '@/components/MkSwitch.vue'; const props = defineProps<{ user: Misskey.entities.User; initialComment?: string; + initialNoteId?: Misskey.entities.Note['id']; }>(); +const Pagination = { + endpoint: 'users/notes' as const, + limit: 10, + params: { + userId: props.user.id, + }, +}; + const emit = defineEmits<{ (ev: 'closed'): void; }>(); +const abuseNotesId = ref(props.initialNoteId ? [props.initialNoteId] : []); +const page = ref(0); const uiWindow = shallowRef>(); const comment = ref(props.initialComment ?? ''); +function pushAbuseReportNote(v, id) { + if (v) { + abuseNotesId.value.push(id); + } else { + abuseNotesId.value = abuseNotesId.value.filter(noteId => noteId !== id); + } +} + function send() { os.apiWithDialog('users/report-abuse', { userId: props.user.id, comment: comment.value, + noteIds: abuseNotesId.value, }, undefined).then(res => { os.alert({ type: 'success', @@ -69,4 +118,22 @@ function send() { .root { --root-margin: 16px; } +.transition_x_enterActive, +.transition_x_leaveActive { + transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1); +} +.transition_x_enterFrom { + opacity: 0; + transform: translateX(50px); +} +.transition_x_leaveTo { + opacity: 0; + transform: translateX(-50px); +} +.note{ + display: flex; + margin: var(--margin) 0; + align-items: center; + +} diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 14ada9b7f0..4f467213b8 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -102,7 +102,7 @@ export function getAbuseNoteMenu(note: misskey.entities.Note, text: string): Men const u = note.url ?? note.uri ?? `${url}/notes/${note.id}`; os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), { user: note.user, - initialComment: `Note: ${u}\n-----\n`, + initialNoteId: note.id, }, {}, 'closed'); }, };