| @@ -1266,14 +1266,19 @@ admin/views/users.vue: | |||||||
|   user-not-found: "ユーザーが見つかりません" |   user-not-found: "ユーザーが見つかりません" | ||||||
|   lookup: "照会" |   lookup: "照会" | ||||||
|   reset-password: "パスワードをリセット" |   reset-password: "パスワードをリセット" | ||||||
|  |   reset-password-confirm: "パスワードをリセットしますか?" | ||||||
|   password-updated: "パスワードは現在「{password}」です" |   password-updated: "パスワードは現在「{password}」です" | ||||||
|   suspend: "凍結" |   suspend: "凍結" | ||||||
|  |   suspend-confirm: "凍結しますか?" | ||||||
|   suspended: "凍結しました" |   suspended: "凍結しました" | ||||||
|   unsuspend: "凍結の解除" |   unsuspend: "凍結の解除" | ||||||
|  |   unsuspend-confirm: "凍結を解除しますか?" | ||||||
|   unsuspended: "凍結を解除しました" |   unsuspended: "凍結を解除しました" | ||||||
|   verify: "公式アカウントにする" |   verify: "公式アカウントにする" | ||||||
|  |   verify-confirm: "公式アカウントにしますか?" | ||||||
|   verified: "公式アカウントにしました" |   verified: "公式アカウントにしました" | ||||||
|   unverify: "公式アカウントを解除する" |   unverify: "公式アカウントを解除する" | ||||||
|  |   unverify-confirm: "公式アカウントを解除しますか?" | ||||||
|   unverified: "公式アカウントを解除しました" |   unverified: "公式アカウントを解除しました" | ||||||
|   users: |   users: | ||||||
|     title: "ユーザー" |     title: "ユーザー" | ||||||
|   | |||||||
							
								
								
									
										82
									
								
								src/client/app/admin/views/users.user.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/client/app/admin/views/users.user.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | |||||||
|  | <template> | ||||||
|  | <div class="kofvwchc"> | ||||||
|  | 	<div> | ||||||
|  | 		<a :href="user | userPage(null, true)"> | ||||||
|  | 			<mk-avatar class="avatar" :user="user" :disable-link="true"/> | ||||||
|  | 		</a> | ||||||
|  | 	</div> | ||||||
|  | 	<div> | ||||||
|  | 		<header> | ||||||
|  | 			<b><mk-user-name :user="user"/></b> | ||||||
|  | 			<span class="username">@{{ user | acct }}</span> | ||||||
|  | 			<span class="is-admin" v-if="user.isAdmin">admin</span> | ||||||
|  | 			<span class="is-moderator" v-if="user.isModerator">moderator</span> | ||||||
|  | 			<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span> | ||||||
|  | 			<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span> | ||||||
|  | 		</header> | ||||||
|  | 		<div> | ||||||
|  | 			<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span> | ||||||
|  | 		</div> | ||||||
|  | 		<div> | ||||||
|  | 			<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import i18n from '../../i18n'; | ||||||
|  | import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	i18n: i18n('admin/views/users.vue'), | ||||||
|  | 	props: ['user'], | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			faSnowflake | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .kofvwchc | ||||||
|  | 	display flex | ||||||
|  | 	padding 16px 0 | ||||||
|  | 	border-top solid 1px var(--faceDivider) | ||||||
|  |  | ||||||
|  | 	> div:first-child | ||||||
|  | 		> a | ||||||
|  | 			> .avatar | ||||||
|  | 				width 64px | ||||||
|  | 				height 64px | ||||||
|  |  | ||||||
|  | 	> div:last-child | ||||||
|  | 		flex 1 | ||||||
|  | 		padding-left 16px | ||||||
|  |  | ||||||
|  | 		@media (max-width 500px) | ||||||
|  | 			font-size 14px | ||||||
|  |  | ||||||
|  | 		> header | ||||||
|  | 			> .username | ||||||
|  | 				margin-left 8px | ||||||
|  | 				opacity 0.7 | ||||||
|  |  | ||||||
|  | 			> .is-admin | ||||||
|  | 			> .is-moderator | ||||||
|  | 				flex-shrink 0 | ||||||
|  | 				align-self center | ||||||
|  | 				margin 0 0 0 .5em | ||||||
|  | 				padding 1px 6px | ||||||
|  | 				font-size 80% | ||||||
|  | 				border-radius 3px | ||||||
|  | 				background var(--noteHeaderAdminBg) | ||||||
|  | 				color var(--noteHeaderAdminFg) | ||||||
|  |  | ||||||
|  | 			> .is-verified | ||||||
|  | 			> .is-suspended | ||||||
|  | 				margin 0 0 0 .5em | ||||||
|  | 				color #4dabf7 | ||||||
|  | </style> | ||||||
| @@ -3,9 +3,14 @@ | |||||||
| 	<ui-card> | 	<ui-card> | ||||||
| 		<div slot="title"><fa :icon="faTerminal"/> {{ $t('operation') }}</div> | 		<div slot="title"><fa :icon="faTerminal"/> {{ $t('operation') }}</div> | ||||||
| 		<section class="fit-top"> | 		<section class="fit-top"> | ||||||
| 			<ui-input v-model="target" type="text"> | 			<ui-input class="target" v-model="target" type="text"> | ||||||
| 				<span>{{ $t('username-or-userid') }}</span> | 				<span>{{ $t('username-or-userid') }}</span> | ||||||
| 			</ui-input> | 			</ui-input> | ||||||
|  | 			<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button> | ||||||
|  |  | ||||||
|  | 			<div class="user" v-if="user"> | ||||||
|  | 				<x-user :user='user'/> | ||||||
|  | 				<div class="actions"> | ||||||
| 					<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button> | 					<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button> | ||||||
| 					<ui-horizon-group> | 					<ui-horizon-group> | ||||||
| 						<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button> | 						<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button> | ||||||
| @@ -15,8 +20,9 @@ | |||||||
| 						<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button> | 						<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button> | ||||||
| 						<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button> | 						<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button> | ||||||
| 					</ui-horizon-group> | 					</ui-horizon-group> | ||||||
| 			<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button> |  | ||||||
| 					<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea> | 					<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
| 		</section> | 		</section> | ||||||
| 	</ui-card> | 	</ui-card> | ||||||
|  |  | ||||||
| @@ -47,29 +53,7 @@ | |||||||
| 				</ui-select> | 				</ui-select> | ||||||
| 			</ui-horizon-group> | 			</ui-horizon-group> | ||||||
| 			<sequential-entrance animation="entranceFromTop" delay="25"> | 			<sequential-entrance animation="entranceFromTop" delay="25"> | ||||||
| 				<div class="kofvwchc" v-for="user in users" :key="user.id"> | 				<x-user v-for="user in users" :user='user' :key="user.id"/> | ||||||
| 					<div> |  | ||||||
| 						<a :href="user | userPage(null, true)"> |  | ||||||
| 							<mk-avatar class="avatar" :user="user" :disable-link="true"/> |  | ||||||
| 						</a> |  | ||||||
| 					</div> |  | ||||||
| 					<div> |  | ||||||
| 						<header> |  | ||||||
| 							<b><mk-user-name :user="user"/></b> |  | ||||||
| 							<span class="username">@{{ user | acct }}</span> |  | ||||||
| 							<span class="is-admin" v-if="user.isAdmin">admin</span> |  | ||||||
| 							<span class="is-moderator" v-if="user.isModerator">moderator</span> |  | ||||||
| 							<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span> |  | ||||||
| 							<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span> |  | ||||||
| 						</header> |  | ||||||
| 						<div> |  | ||||||
| 							<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span> |  | ||||||
| 						</div> |  | ||||||
| 						<div> |  | ||||||
| 							<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
| 				</div> |  | ||||||
| 			</sequential-entrance> | 			</sequential-entrance> | ||||||
| 			<ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button> | 			<ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button> | ||||||
| 		</section> | 		</section> | ||||||
| @@ -83,10 +67,13 @@ import i18n from '../../i18n'; | |||||||
| import parseAcct from "../../../../misc/acct/parse"; | import parseAcct from "../../../../misc/acct/parse"; | ||||||
| import { faCertificate, faUsers, faTerminal, faSearch, faKey } from '@fortawesome/free-solid-svg-icons'; | import { faCertificate, faUsers, faTerminal, faSearch, faKey } from '@fortawesome/free-solid-svg-icons'; | ||||||
| import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | ||||||
|  | import XUser from './users.user.vue'; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	i18n: i18n('admin/views/users.vue'), | 	i18n: i18n('admin/views/users.vue'), | ||||||
|  | 	components: { | ||||||
|  | 		XUser | ||||||
|  | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| 			user: null, | 			user: null, | ||||||
| @@ -131,6 +118,7 @@ export default Vue.extend({ | |||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	methods: { | 	methods: { | ||||||
|  | 		/** テキストエリアのユーザーを解決する */ | ||||||
| 		async fetchUser() { | 		async fetchUser() { | ||||||
| 			try { | 			try { | ||||||
| 				return await this.$root.api('users/show', this.target.startsWith('@') ? parseAcct(this.target) : { userId: this.target }); | 				return await this.$root.api('users/show', this.target.startsWith('@') ? parseAcct(this.target) : { userId: this.target }); | ||||||
| @@ -149,16 +137,27 @@ export default Vue.extend({ | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		/** テキストエリアから処理対象ユーザーを設定する */ | ||||||
| 		async showUser() { | 		async showUser() { | ||||||
|  | 			this.user = null; | ||||||
| 			const user = await this.fetchUser(); | 			const user = await this.fetchUser(); | ||||||
| 			this.$root.api('admin/show-user', { userId: user.id }).then(info => { | 			this.$root.api('admin/show-user', { userId: user.id }).then(info => { | ||||||
| 				this.user = info; | 				this.user = info; | ||||||
| 			}); | 			}); | ||||||
|  | 			this.target = ''; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		/** 処理対象ユーザーの情報を更新する */ | ||||||
|  | 		async refreshUser() { | ||||||
|  | 			this.$root.api('admin/show-user', { userId: this.user._id }).then(info => { | ||||||
|  | 				this.user = info; | ||||||
|  | 			}); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		async resetPassword() { | 		async resetPassword() { | ||||||
| 			const user = await this.fetchUser(); | 			if (!await this.getConfirmed(this.$t('reset-password-confirm'))) return; | ||||||
| 			this.$root.api('admin/reset-password', { userId: user.id }).then(res => { |  | ||||||
|  | 			this.$root.api('admin/reset-password', { userId: this.user._id }).then(res => { | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					type: 'success', | ||||||
| 					text: this.$t('password-updated', { password: res.password }) | 					text: this.$t('password-updated', { password: res.password }) | ||||||
| @@ -167,11 +166,12 @@ export default Vue.extend({ | |||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		async verifyUser() { | 		async verifyUser() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t('verify-confirm'))) return; | ||||||
|  |  | ||||||
| 			this.verifying = true; | 			this.verifying = true; | ||||||
|  |  | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				const user = await this.fetchUser(); | 				await this.$root.api('admin/verify-user', { userId: this.user._id }); | ||||||
| 				await this.$root.api('admin/verify-user', { userId: user.id }); |  | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					type: 'success', | ||||||
| 					text: this.$t('verified') | 					text: this.$t('verified') | ||||||
| @@ -186,14 +186,17 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			this.verifying = false; | 			this.verifying = false; | ||||||
|  |  | ||||||
|  | 			this.refreshUser(); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		async unverifyUser() { | 		async unverifyUser() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t('unverify-confirm'))) return; | ||||||
|  |  | ||||||
| 			this.unverifying = true; | 			this.unverifying = true; | ||||||
|  |  | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				const user = await this.fetchUser(); | 				await this.$root.api('admin/unverify-user', { userId: this.user._id }); | ||||||
| 				await this.$root.api('admin/unverify-user', { userId: user.id }); |  | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					type: 'success', | ||||||
| 					text: this.$t('unverified') | 					text: this.$t('unverified') | ||||||
| @@ -208,14 +211,17 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			this.unverifying = false; | 			this.unverifying = false; | ||||||
|  |  | ||||||
|  | 			this.refreshUser(); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		async suspendUser() { | 		async suspendUser() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t('suspend-confirm'))) return; | ||||||
|  |  | ||||||
| 			this.suspending = true; | 			this.suspending = true; | ||||||
|  |  | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				const user = await this.fetchUser(); | 				await this.$root.api('admin/suspend-user', { userId: this.user._id }); | ||||||
| 				await this.$root.api('admin/suspend-user', { userId: user.id }); |  | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					type: 'success', | ||||||
| 					text: this.$t('suspended') | 					text: this.$t('suspended') | ||||||
| @@ -230,14 +236,17 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			this.suspending = false; | 			this.suspending = false; | ||||||
|  |  | ||||||
|  | 			this.refreshUser(); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
| 		async unsuspendUser() { | 		async unsuspendUser() { | ||||||
|  | 			if (!await this.getConfirmed(this.$t('unsuspend-confirm'))) return; | ||||||
|  |  | ||||||
| 			this.unsuspending = true; | 			this.unsuspending = true; | ||||||
|  |  | ||||||
| 			const process = async () => { | 			const process = async () => { | ||||||
| 				const user = await this.fetchUser(); | 				await this.$root.api('admin/unsuspend-user', { userId: this.user._id }); | ||||||
| 				await this.$root.api('admin/unsuspend-user', { userId: user.id }); |  | ||||||
| 				this.$root.dialog({ | 				this.$root.dialog({ | ||||||
| 					type: 'success', | 					type: 'success', | ||||||
| 					text: this.$t('unsuspended') | 					text: this.$t('unsuspended') | ||||||
| @@ -252,8 +261,21 @@ export default Vue.extend({ | |||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			this.unsuspending = false; | 			this.unsuspending = false; | ||||||
|  |  | ||||||
|  | 			this.refreshUser(); | ||||||
| 		}, | 		}, | ||||||
|  |  | ||||||
|  | 		async getConfirmed(text: string): Promise<Boolean> { | ||||||
|  | 			const confirm = await this.$root.dialog({ | ||||||
|  | 				type: 'warning', | ||||||
|  | 				showCancelButton: true, | ||||||
|  | 				title: 'confirm', | ||||||
|  | 				text, | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			return !confirm.canceled; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		fetchUsers() { | 		fetchUsers() { | ||||||
| 			this.$root.api('admin/show-users', { | 			this.$root.api('admin/show-users', { | ||||||
| 				state: this.state, | 				state: this.state, | ||||||
| @@ -277,42 +299,12 @@ export default Vue.extend({ | |||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="stylus" scoped> | <style lang="stylus" scoped> | ||||||
| .kofvwchc | .target | ||||||
| 	display flex | 	margin-bottom 16px !important | ||||||
| 	padding 16px 0 |  | ||||||
| 	border-top solid 1px var(--faceDivider) |  | ||||||
|  |  | ||||||
| 	> div:first-child | .user | ||||||
| 		> a | 	margin-top 32px | ||||||
| 			> .avatar |  | ||||||
| 				width 64px |  | ||||||
| 				height 64px |  | ||||||
|  |  | ||||||
| 	> div:last-child | 	> .actions | ||||||
| 		flex 1 | 		margin-left 80px | ||||||
| 		padding-left 16px |  | ||||||
|  |  | ||||||
| 		@media (max-width 500px) |  | ||||||
| 			font-size 14px |  | ||||||
|  |  | ||||||
| 		> header |  | ||||||
| 			> .username |  | ||||||
| 				margin-left 8px |  | ||||||
| 				opacity 0.7 |  | ||||||
|  |  | ||||||
| 			> .is-admin |  | ||||||
| 			> .is-moderator |  | ||||||
| 				flex-shrink 0 |  | ||||||
| 				align-self center |  | ||||||
| 				margin 0 0 0 .5em |  | ||||||
| 				padding 1px 6px |  | ||||||
| 				font-size 80% |  | ||||||
| 				border-radius 3px |  | ||||||
| 				background var(--noteHeaderAdminBg) |  | ||||||
| 				color var(--noteHeaderAdminFg) |  | ||||||
|  |  | ||||||
| 			> .is-verified |  | ||||||
| 			> .is-suspended |  | ||||||
| 				margin 0 0 0 .5em |  | ||||||
| 				color #4dabf7 |  | ||||||
| </style> | </style> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 MeiMei
					MeiMei