Skip to content

Commit

Permalink
enh(MiAbuseUserReport): 通報一覧ページの改修
Browse files Browse the repository at this point in the history
* fix(admin/abuse-user-reports): forwardedが含まれていない問題

* fix(MkAbuseReport): 通報一覧で描画が崩れる問題
  • Loading branch information
taiyme committed May 23, 2024
1 parent bad02fd commit 6bad0a0
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 101 deletions.
8 changes: 8 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10009,6 +10009,14 @@ export interface Locale extends ILocale {
* 数字引用しました
*/
readonly "didNumberquote": string;
/**
* {user}によって解決済み
*/
readonly "resolvedBy": ParameterizedString<"user">;
/**
* リモートサーバーに転送済み
*/
readonly "forwardedReport": string;
readonly "_about": {
/**
* taiymeについて
Expand Down
2 changes: 2 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2666,6 +2666,8 @@ _tms:
numberquote: "数字引用する"
didPakuru: "パクりました"
didNumberquote: "数字引用しました"
resolvedBy: "{user}によって解決済み"
forwardedReport: "リモートサーバーに転送済み"
_about:
title: "taiymeについて"
description: "taiymeは、Misskeyから派生したオープンソースのソフトウェアです。"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export const meta = {
nullable: true, optional: true,
ref: 'UserDetailedNotMe',
},
forwarded: {
type: 'boolean',
nullable: false, optional: false,
},
},
},
},
Expand Down
229 changes: 152 additions & 77 deletions packages/frontend/src/components/MkAbuseReport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,112 +4,187 @@ SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<div class="bcekxzvu _margin _panel">
<div class="target">
<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
<MkAvatar class="avatar" :user="report.targetUser" indicator/>
<div class="names">
<MkUserName class="name" :user="report.targetUser"/>
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
</div>
<div :class="[$style.root, '_panel', '_margin']">
<!-- 通報されたユーザー -->
<div :class="$style.item">
<MkA v-user-preview="props.report.targetUser.id" :to="`/admin/user/${props.report.targetUser.id}`" :behavior="'window'">
<MkUserCardMini :user="props.report.targetUser" :withChart="false" :class="$style.userCard"/>
</MkA>
<MkKeyValue>
<template #key>{{ i18n.ts.registeredDate }}</template>
<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
</MkKeyValue>
</div>
<div class="detail">
<div>
<Mfm :text="report.comment" :linkNavigationBehavior="'window'"/>

<!-- 通報の本文 -->
<div :class="$style.item">
<Mfm :text="props.report.comment" :linkNavigationBehavior="'window'"/>
</div>

<!-- 通報の情報 -->
<div :class="$style.item">
<div :class="$style.information">
<div :class="$style.label">{{ i18n.ts.reporter }}</div>
<div :class="$style.content">
<MkA :to="`/admin/user/${props.report.reporter.id}`" class="_link" :behavior="'window'">
<MkAcct :user="props.report.reporter"/>
</MkA>
</div>
<div :class="$style.label">{{ i18n.ts.createdAt }}</div>
<div :class="$style.content">{{ dateString(props.report.createdAt) }} (<MkTime :time="props.report.createdAt"/>)</div>
</div>
<hr/>
<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></div>
<div v-if="report.assignee">
{{ i18n.ts.moderator }}:
<MkAcct :user="report.assignee"/>
</div>

<!-- 通報の状態 -->
<div :class="$style.item">
<div v-if="resolvedRef">
<i class="ti ti-check" style="color: var(--success); margin-right: 0.5em;"></i>
<I18n v-if="props.report.assignee" :src="i18n.ts._tms.resolvedBy" tag="span">
<template #user>
<MkA :to="`/admin/user/${props.report.assignee.id}`" class="_link" :behavior="'window'">
<MkAcct :user="props.report.assignee"/>
</MkA>
</template>
</I18n>
<span v-else>{{ i18n.ts.resolved }}</span>
</div>
<div v-if="forwardedRef">
<i class="ti ti-check" style="color: var(--success); margin-right: 0.5em;"></i>
<span>{{ i18n.ts._tms.forwardedReport }}</span>
</div>
<div><MkTime :time="report.createdAt"/></div>
<div class="action">
<MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved">
{{ i18n.ts.forwardReport }}
<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
</div>

<!-- 通報の操作 -->
<div :class="$style.item">
<div v-if="!resolvedRef" :class="$style.operations">
<MkSwitch v-if="props.report.targetUser.host != null" v-model="editForwardRef">
<template #label>{{ i18n.ts.forwardReport }}</template>
</MkSwitch>
<MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
<div :class="$style.resolveButton">
<MkButton primary @click="resolveReport">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
</div>
</div>
<div v-else-if="!forwardedRef && props.report.targetUser.host != null" :class="$style.operations">
<div :class="$style.resolveButton">
<MkButton @click="forwardReport">{{ i18n.ts.forwardReport }}</MkButton>
</div>
</div>
</div>
</div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import * as os from '@/os.js';
import type * as Misskey from 'misskey-js';
import { apiWithDialog } from '@/os.js';
import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
export type AbuseUserReport = Misskey.entities.AdminAbuseUserReportsResponse[number];
const props = defineProps<{
report: any;
report: AbuseUserReport;
}>();
const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
resolved: [reportId: string];
}>();
const forward = ref(props.report.forwarded);
const editForwardRef = ref(false);
const resolvedRef = ref(props.report.resolved);
const forwardedRef = ref(props.report.forwarded);
const resolveReport = () => {
// 解決済みは弾く
if (resolvedRef.value) return;
const reportId = props.report.id;
const forward = editForwardRef.value && props.report.targetUser.host != null;
function resolve() {
os.apiWithDialog('admin/resolve-abuse-user-report', {
forward: forward.value,
reportId: props.report.id,
}).then(() => {
emit('resolved', props.report.id);
apiWithDialog('admin/resolve-abuse-user-report', { reportId, forward }).then(() => {
emit('resolved', reportId);
resolvedRef.value = true;
forwardedRef.value = forward;
});
}
};
const forwardReport = () => {
// 未解決は弾く
if (!resolvedRef.value) return;
// ローカルユーザーは弾く
if (props.report.targetUser.host == null) return;
const reportId = props.report.id;
const forward = true;
apiWithDialog('admin/resolve-abuse-user-report', { reportId, forward }).then(() => {
resolvedRef.value = true;
forwardedRef.value = forward;
});
};
</script>

<style lang="scss" scoped>
.bcekxzvu {
<style lang="scss" module>
.root {
display: flex;
flex-direction: column;
}
.item {
padding: 16px;
border-top: solid 0.5px var(--divider);
&:first-child {
border-top: none;
}
&:empty {
display: none;
}
}
.userCard {
background-image: linear-gradient(
45deg,
rgba(255, 196, 0, 0.15) 16.67%,
transparent 16.67%,
transparent 50%,
rgba(255, 196, 0, 0.15) 50%,
rgba(255, 196, 0, 0.15) 66.67%,
transparent 66.67%,
transparent 100%
);
background-size: 16px 16px;
background-color: transparent !important;
}
.information {
display: grid;
grid-template-columns: max-content 1fr;
gap: 0px 8px;
> .target {
width: 35%;
box-sizing: border-box;
text-align: left;
padding: 24px;
border-right: solid 1px var(--divider);
> .info {
display: flex;
box-sizing: border-box;
align-items: center;
padding: 14px;
border-radius: 8px;
--c: rgb(255 196 0 / 15%);
background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
background-size: 16px 16px;
> .avatar {
width: 42px;
height: 42px;
}
> .names {
margin-left: 0.3em;
padding: 0 8px;
flex: 1;
> .name {
font-weight: bold;
}
}
}
> .label {
opacity: 0.75;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
> .detail {
flex: 1;
padding: 24px;
> .content {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.operations {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.resolveButton {
margin-left: auto;
}
</style>
38 changes: 14 additions & 24 deletions packages/frontend/src/pages/admin/abuses.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="remote">{{ i18n.ts.remote }}</option>
</MkSelect>
</div>
<!-- TODO
<div class="inputs" style="display: flex; padding-top: 1.2em;">
<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" :spellcheck="false">
<span>{{ i18n.ts.username }}</span>
</MkInput>
<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params().origin === 'local'">
<span>{{ i18n.ts.host }}</span>
</MkInput>
</div>
-->

<MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
<MkPagination v-slot="{ items }" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
<XAbuseReport v-for="report in (items as AbuseUserReport[])" :key="report.id" :report="report" @resolved="resolved"/>
</MkPagination>
</div>
</div>
Expand All @@ -52,20 +42,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>

<script lang="ts" setup>
import { computed, shallowRef, ref } from 'vue';
import MkSelect from '@/components/MkSelect.vue';
import MkPagination from '@/components/MkPagination.vue';
import XAbuseReport from '@/components/MkAbuseReport.vue';
import { computed, ref, shallowRef } from 'vue';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkPagination from '@/components/MkPagination.vue';
import MkSelect from '@/components/MkSelect.vue';
import XAbuseReport, { type AbuseUserReport } from '@/components/MkAbuseReport.vue';
const reports = shallowRef<InstanceType<typeof MkPagination>>();
const state = ref('unresolved');
const reporterOrigin = ref('combined');
const targetUserOrigin = ref('combined');
const searchUsername = ref('');
const searchHost = ref('');
const state = ref<'all' | 'unresolved' | 'resolved'>('unresolved');
const reporterOrigin = ref<'combined' | 'local' | 'remote'>('combined');
const targetUserOrigin = ref<'combined' | 'local' | 'remote'>('combined');
const pagination = {
endpoint: 'admin/abuse-user-reports' as const,
Expand All @@ -77,9 +65,11 @@ const pagination = {
})),
};
function resolved(reportId) {
reports.value.removeItem(reportId);
}
const resolved = (reportId: string) => {
if (state.value === 'unresolved') {
reports.value?.removeItem(reportId);
}
};
const headerActions = computed(() => []);
Expand Down
1 change: 1 addition & 0 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5080,6 +5080,7 @@ export type operations = {
reporter: components['schemas']['UserDetailedNotMe'];
targetUser: components['schemas']['UserDetailedNotMe'];
assignee?: components['schemas']['UserDetailedNotMe'] | null;
forwarded: boolean;
})[];
};
};
Expand Down

0 comments on commit 6bad0a0

Please sign in to comment.