enhance(backend/frontend): モデレーションノートをユーザーのプロフィールページからも閲覧および編集できるように

This commit is contained in:
syuilo 2023-05-06 09:51:06 +09:00
parent bd1c2abffc
commit 10ff379b4c
3 changed files with 31 additions and 10 deletions

View file

@ -34,6 +34,7 @@
* デフォルトがオフになるので、ロールタイムラインを表示する場合はオンにしてください。 * デフォルトがオフになるので、ロールタイムラインを表示する場合はオンにしてください。
- ロールに強制的にNSFWを付与するポリシーを追加 - ロールに強制的にNSFWを付与するポリシーを追加
* アップロード済みのファイルはNSFWにならない為注意してください。 * アップロード済みのファイルはNSFWにならない為注意してください。
- モデレーションノートがユーザーのプロフィールページからも閲覧および編集できるようになりました。
- カスタム絵文字のライセンスを複数でセットできるようになりました。 - カスタム絵文字のライセンスを複数でセットできるようになりました。
- 管理者が予約ユーザー名を設定できるようになりました。 - 管理者が予約ユーザー名を設定できるようになりました。
- Fix: フォローリクエストの通知が残る問題を修正 - Fix: フォローリクエストの通知が残る問題を修正

View file

@ -292,7 +292,7 @@ export class UserEntityService implements OnModuleInit {
public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>( public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User, src: User['id'] | User,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id']; isRoot: boolean; } | null | undefined,
options?: { options?: {
detail?: D, detail?: D,
includeSecrets?: boolean, includeSecrets?: boolean,
@ -308,6 +308,7 @@ export class UserEntityService implements OnModuleInit {
const meId = me ? me.id : null; const meId = me ? me.id : null;
const isMe = meId === user.id; const isMe = meId === user.id;
const iAmModerator = me ? await this.roleService.isModerator(me) : false;
const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null; const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin') const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin')
@ -411,6 +412,7 @@ export class UserEntityService implements OnModuleInit {
userId: meId, userId: meId,
targetUserId: user.id, targetUserId: user.id,
}).then(row => row?.memo ?? null), }).then(row => row?.memo ?? null),
moderationNote: iAmModerator ? profile!.moderationNote : null,
} : {}), } : {}),
...(opts.detail && isMe ? { ...(opts.detail && isMe ? {

View file

@ -7,7 +7,7 @@
<!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> --> <!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
<div class="profile _gaps"> <div class="profile _gaps">
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo" /> <MkAccountMoved v-if="user.movedTo" :moved-to="user.movedTo"/>
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/> <MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
<div :key="user.id" class="main _panel"> <div :key="user.id" class="main _panel">
@ -42,6 +42,17 @@
<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span> <span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
</div> </div>
</div> </div>
<div v-if="user.roles.length > 0" class="roles">
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
{{ role.name }}
</span>
</div>
<div v-if="iAmModerator" class="moderationNote">
<MkTextarea v-model="moderationNote" manual-save>
<template #label>Moderation note</template>
</MkTextarea>
</div>
<div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}"> <div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}">
<div class="heading" v-text="i18n.ts.memo"/> <div class="heading" v-text="i18n.ts.memo"/>
<textarea <textarea
@ -53,12 +64,6 @@
@input="adjustMemoTextarea" @input="adjustMemoTextarea"
/> />
</div> </div>
<div v-if="user.roles.length > 0" class="roles">
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
{{ role.name }}
</span>
</div>
<div class="description"> <div class="description">
<MkOmit> <MkOmit>
<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/> <Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
@ -134,6 +139,7 @@ import MkNote from '@/components/MkNote.vue';
import MkFollowButton from '@/components/MkFollowButton.vue'; import MkFollowButton from '@/components/MkFollowButton.vue';
import MkAccountMoved from '@/components/MkAccountMoved.vue'; import MkAccountMoved from '@/components/MkAccountMoved.vue';
import MkRemoteCaution from '@/components/MkRemoteCaution.vue'; import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkOmit from '@/components/MkOmit.vue'; import MkOmit from '@/components/MkOmit.vue';
import MkInfo from '@/components/MkInfo.vue'; import MkInfo from '@/components/MkInfo.vue';
import { getScrollPosition } from '@/scripts/scroll'; import { getScrollPosition } from '@/scripts/scroll';
@ -143,7 +149,7 @@ import { userPage } from '@/filters/user';
import * as os from '@/os'; import * as os from '@/os';
import { useRouter } from '@/router'; import { useRouter } from '@/router';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { $i } from '@/account'; import { $i, iAmModerator } from '@/account';
import { dateString } from '@/filters/date'; import { dateString } from '@/filters/date';
import { confetti } from '@/scripts/confetti'; import { confetti } from '@/scripts/confetti';
import MkNotes from '@/components/MkNotes.vue'; import MkNotes from '@/components/MkNotes.vue';
@ -168,8 +174,12 @@ let rootEl = $ref<null | HTMLElement>(null);
let bannerEl = $ref<null | HTMLElement>(null); let bannerEl = $ref<null | HTMLElement>(null);
let memoTextareaEl = $ref<null | HTMLElement>(null); let memoTextareaEl = $ref<null | HTMLElement>(null);
let memoDraft = $ref(props.user.memo); let memoDraft = $ref(props.user.memo);
let isEditingMemo = $ref(false); let isEditingMemo = $ref(false);
let moderationNote = $ref(props.user.moderationNote);
watch($$(moderationNote), async () => {
await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote });
});
const pagination = { const pagination = {
endpoint: 'users/notes' as const, endpoint: 'users/notes' as const,
@ -426,6 +436,10 @@ onUnmounted(() => {
} }
} }
> .moderationNote {
margin: 12px 24px 0 154px;
}
> .memo { > .memo {
margin: 12px 24px 0 154px; margin: 12px 24px 0 154px;
background: transparent; background: transparent;
@ -593,6 +607,10 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
} }
> .moderationNote {
margin: 16px 16px 0 16px;
}
> .memo { > .memo {
margin: 16px 16px 0 16px; margin: 16px 16px 0 16px;
} }