enhance(frontend): tweak user moderation page
This commit is contained in:
		| @@ -6,7 +6,7 @@ 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="`/user-info/${report.targetUserId}`"> | ||||
| 		<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`"> | ||||
| 			<MkAvatar class="avatar" :user="report.targetUser" indicator/> | ||||
| 			<div class="names"> | ||||
| 				<MkUserName class="name" :user="report.targetUser"/> | ||||
|   | ||||
| @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 					<template #value><span class="_monospace"><MkTime :time="file.createdAt" mode="detail" style="display: block;"/></span></template> | ||||
| 				</MkKeyValue> | ||||
| 			</div> | ||||
| 			<MkA v-if="file.user" class="user" :to="`/user-info/${file.user.id}`"> | ||||
| 			<MkA v-if="file.user" class="user" :to="`/admin/user/${file.user.id}`"> | ||||
| 				<MkUserCardMini :user="file.user"/> | ||||
| 			</MkA> | ||||
| 			<div> | ||||
|   | ||||
| @@ -24,12 +24,6 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 
 | ||||
| 				<MkInfo v-if="user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo> | ||||
| 
 | ||||
| 				<div v-if="user.url" class="_formLinksGrid"> | ||||
| 					<FormLink :to="userPage(user)">Profile</FormLink> | ||||
| 					<FormLink :to="user.url" :external="true">Profile (remote)</FormLink> | ||||
| 				</div> | ||||
| 				<FormLink v-else :to="userPage(user)">Profile</FormLink> | ||||
| 
 | ||||
| 				<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink> | ||||
| 
 | ||||
| 				<div style="display: flex; flex-direction: column; gap: 1em;"> | ||||
| @@ -57,6 +51,11 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 					</MkKeyValue> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<MkTextarea v-model="moderationNote" manualSave> | ||||
| 					<template #label>Moderation note</template> | ||||
| 				</MkTextarea> | ||||
| 
 | ||||
| 				<!-- | ||||
| 				<FormSection> | ||||
| 					<template #label>ActivityPub</template> | ||||
| 
 | ||||
| @@ -90,95 +89,85 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 						</MkFolder> | ||||
| 					</div> | ||||
| 				</FormSection> | ||||
| 			</div> | ||||
| 			--> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'moderation'" class="_gaps_m"> | ||||
| 				<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch> | ||||
| 
 | ||||
| 				<div> | ||||
| 					<MkButton v-if="user.host == null && iAmModerator" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton> | ||||
| 					<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-license"></i></template> | ||||
| 					<template #label>{{ i18n.ts._role.policies }}</template> | ||||
| 				<FormSection> | ||||
| 					<div class="_gaps"> | ||||
| 						<div v-for="policy in Object.keys(info.policies)" :key="policy"> | ||||
| 							{{ policy }} ... {{ info.policies[policy] }} | ||||
| 						<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch> | ||||
| 
 | ||||
| 						<div> | ||||
| 							<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-badges"></i></template> | ||||
| 					<template #label>{{ i18n.ts.roles }}</template> | ||||
| 					<div class="_gaps"> | ||||
| 						<MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> | ||||
| 
 | ||||
| 						<div v-for="role in info.roles" :key="role.id" :class="$style.roleItem"> | ||||
| 							<div :class="$style.roleItemMain"> | ||||
| 								<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/> | ||||
| 								<button class="_button" :class="$style.roleToggle" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button> | ||||
| 								<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button> | ||||
| 								<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> | ||||
| 						<MkFolder> | ||||
| 							<template #icon><i class="ti ti-license"></i></template> | ||||
| 							<template #label>{{ i18n.ts._role.policies }}</template> | ||||
| 							<div class="_gaps"> | ||||
| 								<div v-for="policy in Object.keys(info.policies)" :key="policy"> | ||||
| 									{{ policy }} ... {{ info.policies[policy] }} | ||||
| 								</div> | ||||
| 							</div> | ||||
| 							<div v-if="expandedRoles.includes(role.id)" :class="$style.roleItemSub"> | ||||
| 								<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div> | ||||
| 								<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div> | ||||
| 								<div v-else>Period: {{ i18n.ts.indefinitely }}</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder v-if="user.host == null && iAmModerator"> | ||||
| 					<template #icon><i class="ti ti-speakerphone"></i></template> | ||||
| 					<template #label>{{ i18n.ts.announcements }}</template> | ||||
| 					<div class="_gaps"> | ||||
| 						<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton> | ||||
| 
 | ||||
| 						<MkPagination :pagination="announcementsPagination"> | ||||
| 							<template #default="{ items }"> | ||||
| 								<div class="_gaps_s"> | ||||
| 									<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)"> | ||||
| 										<span style="margin-right: 0.5em;"> | ||||
| 											<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> | ||||
| 											<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> | ||||
| 											<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> | ||||
| 											<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> | ||||
| 										</span> | ||||
| 										<span>{{ announcement.title }}</span> | ||||
| 										<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span> | ||||
| 									</div> | ||||
| 						<MkFolder> | ||||
| 							<template #icon><i class="ti ti-password"></i></template> | ||||
| 							<template #label>IP</template> | ||||
| 							<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo> | ||||
| 							<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo> | ||||
| 							<template v-if="iAmAdmin && ips"> | ||||
| 								<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;"> | ||||
| 									<span class="date">{{ record.createdAt }}</span> | ||||
| 									<span class="ip">{{ record.ip }}</span> | ||||
| 								</div> | ||||
| 							</template> | ||||
| 						</MkPagination> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 						</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-password"></i></template> | ||||
| 					<template #label>IP</template> | ||||
| 					<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo> | ||||
| 					<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo> | ||||
| 					<template v-if="iAmAdmin && ips"> | ||||
| 						<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;"> | ||||
| 							<span class="date">{{ record.createdAt }}</span> | ||||
| 							<span class="ip">{{ record.ip }}</span> | ||||
| 						<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton> | ||||
| 					</div> | ||||
| 				</FormSection> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'roles'" class="_gaps"> | ||||
| 				<MkButton v-if="user.host == null" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> | ||||
| 
 | ||||
| 				<div v-for="role in info.roles" :key="role.id" :class="$style.roleItem"> | ||||
| 					<div :class="$style.roleItemMain"> | ||||
| 						<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/> | ||||
| 						<button class="_button" :class="$style.roleToggle" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button> | ||||
| 						<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button> | ||||
| 						<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> | ||||
| 					</div> | ||||
| 					<div v-if="expandedRoles.includes(role.id)" :class="$style.roleItemSub"> | ||||
| 						<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div> | ||||
| 						<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div> | ||||
| 						<div v-else>Period: {{ i18n.ts.indefinitely }}</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'announcements'" class="_gaps"> | ||||
| 				<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton> | ||||
| 
 | ||||
| 				<MkPagination :pagination="announcementsPagination"> | ||||
| 					<template #default="{ items }"> | ||||
| 						<div class="_gaps_s"> | ||||
| 							<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)"> | ||||
| 								<span style="margin-right: 0.5em;"> | ||||
| 									<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> | ||||
| 									<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> | ||||
| 									<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> | ||||
| 									<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> | ||||
| 								</span> | ||||
| 								<span>{{ announcement.title }}</span> | ||||
| 								<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</template> | ||||
| 				</MkFolder> | ||||
| 				</MkPagination> | ||||
| 			</div> | ||||
| 
 | ||||
| 				<MkFolder> | ||||
| 					<template #icon><i class="ti ti-cloud"></i></template> | ||||
| 					<template #label>{{ i18n.ts.files }}</template> | ||||
| 					<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/> | ||||
| 				</MkFolder> | ||||
| 
 | ||||
| 				<MkTextarea v-model="moderationNote" manualSave> | ||||
| 					<template #label>Moderation note</template> | ||||
| 				</MkTextarea> | ||||
| 			<div v-else-if="tab === 'drive'" class="_gaps"> | ||||
| 				<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div v-else-if="tab === 'chart'" class="_gaps_m"> | ||||
| @@ -230,7 +219,7 @@ import { url } from '@/config'; | ||||
| import { userPage, acct } from '@/filters/user'; | ||||
| import { definePageMetadata } from '@/scripts/page-metadata'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { iAmAdmin, iAmModerator, $i } from '@/account'; | ||||
| import { iAmAdmin, $i } from '@/account'; | ||||
| import MkRolePreview from '@/components/MkRolePreview.vue'; | ||||
| import MkPagination, { Paging } from '@/components/MkPagination.vue'; | ||||
| 
 | ||||
| @@ -269,34 +258,26 @@ const announcementsPagination = { | ||||
| let expandedRoles = $ref([]); | ||||
| 
 | ||||
| function createFetcher() { | ||||
| 	if (iAmModerator) { | ||||
| 		return () => Promise.all([os.api('users/show', { | ||||
| 			userId: props.userId, | ||||
| 		}), os.api('admin/show-user', { | ||||
| 			userId: props.userId, | ||||
| 		}), iAmAdmin ? os.api('admin/get-user-ips', { | ||||
| 			userId: props.userId, | ||||
| 		}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => { | ||||
| 			user = _user; | ||||
| 			info = _info; | ||||
| 			ips = _ips; | ||||
| 			moderator = info.isModerator; | ||||
| 			silenced = info.isSilenced; | ||||
| 			suspended = info.isSuspended; | ||||
| 			moderationNote = info.moderationNote; | ||||
| 	return () => Promise.all([os.api('users/show', { | ||||
| 		userId: props.userId, | ||||
| 	}), os.api('admin/show-user', { | ||||
| 		userId: props.userId, | ||||
| 	}), iAmAdmin ? os.api('admin/get-user-ips', { | ||||
| 		userId: props.userId, | ||||
| 	}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => { | ||||
| 		user = _user; | ||||
| 		info = _info; | ||||
| 		ips = _ips; | ||||
| 		moderator = info.isModerator; | ||||
| 		silenced = info.isSilenced; | ||||
| 		suspended = info.isSuspended; | ||||
| 		moderationNote = info.moderationNote; | ||||
| 
 | ||||
| 			watch($$(moderationNote), async () => { | ||||
| 				await os.api('admin/update-user-note', { userId: user.id, text: moderationNote }); | ||||
| 				await refreshUser(); | ||||
| 			}); | ||||
| 		watch($$(moderationNote), async () => { | ||||
| 			await os.api('admin/update-user-note', { userId: user.id, text: moderationNote }); | ||||
| 			await refreshUser(); | ||||
| 		}); | ||||
| 	} else { | ||||
| 		return () => os.api('users/show', { | ||||
| 			userId: props.userId, | ||||
| 		}).then((res) => { | ||||
| 			user = res; | ||||
| 		}); | ||||
| 	} | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
| function refreshUser() { | ||||
| @@ -472,11 +453,19 @@ const headerTabs = $computed(() => [{ | ||||
| 	key: 'overview', | ||||
| 	title: i18n.ts.overview, | ||||
| 	icon: 'ti ti-info-circle', | ||||
| }, iAmModerator ? { | ||||
| 	key: 'moderation', | ||||
| 	title: i18n.ts.moderation, | ||||
| 	icon: 'ti ti-user-exclamation', | ||||
| } : null, { | ||||
| }, { | ||||
| 	key: 'roles', | ||||
| 	title: i18n.ts.roles, | ||||
| 	icon: 'ti ti-badges', | ||||
| }, { | ||||
| 	key: 'announcements', | ||||
| 	title: i18n.ts.announcements, | ||||
| 	icon: 'ti ti-speakerphone', | ||||
| }, { | ||||
| 	key: 'drive', | ||||
| 	title: i18n.ts.drive, | ||||
| 	icon: 'ti ti-cloud', | ||||
| }, { | ||||
| 	key: 'chart', | ||||
| 	title: i18n.ts.charts, | ||||
| 	icon: 'ti ti-chart-line', | ||||
| @@ -484,11 +473,11 @@ const headerTabs = $computed(() => [{ | ||||
| 	key: 'raw', | ||||
| 	title: 'Raw', | ||||
| 	icon: 'ti ti-code', | ||||
| }].filter(x => x != null)); | ||||
| }]); | ||||
| 
 | ||||
| definePageMetadata(computed(() => ({ | ||||
| 	title: user ? acct(user) : i18n.ts.userInfo, | ||||
| 	icon: 'ti ti-info-circle', | ||||
| 	icon: 'ti ti-user-exclamation', | ||||
| }))); | ||||
| </script> | ||||
| 
 | ||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 	<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> | ||||
| 		<MkLoading v-if="fetching"/> | ||||
| 		<div v-else :class="$style.root" class="_panel"> | ||||
| 			<MkA v-for="user in moderators" :key="user.id" class="user" :to="`/user-info/${user.id}`"> | ||||
| 			<MkA v-for="user in moderators" :key="user.id" class="user" :to="`/admin/user/${user.id}`"> | ||||
| 				<MkAvatar :user="user" class="avatar" indicator/> | ||||
| 			</MkA> | ||||
| 		</div> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 	<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in"> | ||||
| 		<MkLoading v-if="fetching"/> | ||||
| 		<div v-else class="users"> | ||||
| 			<MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/user-info/${user.id}`" class="user"> | ||||
| 			<MkA v-for="(user, i) in newUsers" :key="user.id" :to="`/admin/user/${user.id}`" class="user"> | ||||
| 				<MkUserCardMini :user="user"/> | ||||
| 			</MkA> | ||||
| 		</div> | ||||
|   | ||||
| @@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 								<div class="_gaps_s"> | ||||
| 									<div v-for="item in items" :key="item.user.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedItems.includes(item.id) }]"> | ||||
| 										<div :class="$style.userItemMain"> | ||||
| 											<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.user.id}`"> | ||||
| 											<MkA :class="$style.userItemMainBody" :to="`/admin/user/${item.user.id}`"> | ||||
| 												<MkUserCardMini :user="item.user"/> | ||||
| 											</MkA> | ||||
| 											<button class="_button" :class="$style.userToggle" @click="toggleItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> | ||||
|   | ||||
| @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
|  | ||||
| 				<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination"> | ||||
| 					<div :class="$style.users"> | ||||
| 						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/user-info/${user.id}`"> | ||||
| 						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" :class="$style.user" :to="`/admin/user/${user.id}`"> | ||||
| 							<MkUserCardMini :user="user"/> | ||||
| 						</MkA> | ||||
| 					</div> | ||||
| @@ -116,7 +116,7 @@ async function addUser() { | ||||
| } | ||||
|  | ||||
| function show(user) { | ||||
| 	os.pageWindow(`/user-info/${user.id}`); | ||||
| 	os.pageWindow(`/admin/user/${user.id}`); | ||||
| } | ||||
|  | ||||
| const headerActions = $computed(() => [{ | ||||
|   | ||||
| @@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 		</div> | ||||
| 		<div v-else-if="tab === 'users'" class="_gaps_m"> | ||||
| 			<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;"> | ||||
| 				<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`"> | ||||
| 				<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`"> | ||||
| 					<MkUserCardMini :user="user"/> | ||||
| 				</MkA> | ||||
| 			</MkPagination> | ||||
|   | ||||
| @@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 				<div class="_gaps_s"> | ||||
| 					<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]"> | ||||
| 						<div :class="$style.userItemMain"> | ||||
| 							<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.mutee.id}`"> | ||||
| 							<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)"> | ||||
| 								<MkUserCardMini :user="item.mutee"/> | ||||
| 							</MkA> | ||||
| 							<button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> | ||||
| @@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 				<div class="_gaps_s"> | ||||
| 					<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]"> | ||||
| 						<div :class="$style.userItemMain"> | ||||
| 							<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.mutee.id}`"> | ||||
| 							<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)"> | ||||
| 								<MkUserCardMini :user="item.mutee"/> | ||||
| 							</MkA> | ||||
| 							<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> | ||||
| @@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only | ||||
| 				<div class="_gaps_s"> | ||||
| 					<div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]"> | ||||
| 						<div :class="$style.userItemMain"> | ||||
| 							<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.blockee.id}`"> | ||||
| 							<MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)"> | ||||
| 								<MkUserCardMini :user="item.blockee"/> | ||||
| 							</MkA> | ||||
| 							<button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button> | ||||
|   | ||||
| @@ -42,10 +42,6 @@ export const routes = [{ | ||||
| }, { | ||||
| 	path: '/clips/:clipId', | ||||
| 	component: page(() => import('./pages/clip.vue')), | ||||
| }, { | ||||
| 	path: '/user-info/:userId', | ||||
| 	component: page(() => import('./pages/user-info.vue')), | ||||
| 	hash: 'initialTab', | ||||
| }, { | ||||
| 	path: '/instance-info/:host', | ||||
| 	component: page(() => import('./pages/instance-info.vue')), | ||||
| @@ -334,6 +330,9 @@ export const routes = [{ | ||||
| }, { | ||||
| 	path: '/registry', | ||||
| 	component: page(() => import('./pages/registry.vue')), | ||||
| }, { | ||||
| 	path: '/admin/user/:userId', | ||||
| 	component: iAmModerator ? page(() => import('./pages/admin-user.vue')) : page(() => import('./pages/not-found.vue')), | ||||
| }, { | ||||
| 	path: '/admin/file/:fileId', | ||||
| 	component: iAmModerator ? page(() => import('./pages/admin-file.vue')) : page(() => import('./pages/not-found.vue')), | ||||
|   | ||||
| @@ -133,13 +133,13 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router | ||||
| 		action: () => { | ||||
| 			copyToClipboard(`@${user.username}@${user.host ?? host}`); | ||||
| 		}, | ||||
| 	}, { | ||||
| 		icon: 'ti ti-info-circle', | ||||
| 		text: i18n.ts.info, | ||||
| 	}, ...(iAmModerator ? [{ | ||||
| 		icon: 'ti ti-user-exclamation', | ||||
| 		text: i18n.ts.moderation, | ||||
| 		action: () => { | ||||
| 			router.push(`/user-info/${user.id}`); | ||||
| 			router.push(`/admin/user/${user.id}`); | ||||
| 		}, | ||||
| 	}, { | ||||
| 	}] : []), { | ||||
| 		icon: 'ti ti-rss', | ||||
| 		text: i18n.ts.copyRSS, | ||||
| 		action: () => { | ||||
|   | ||||
| @@ -14,7 +14,7 @@ export async function lookupUser() { | ||||
| 	if (canceled) return; | ||||
|  | ||||
| 	const show = (user) => { | ||||
| 		os.pageWindow(`/user-info/${user.id}`); | ||||
| 		os.pageWindow(`/admin/user/${user.id}`); | ||||
| 	}; | ||||
|  | ||||
| 	const usernamePromise = os.api('users/show', Acct.parse(result)); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo