From 95095ee8d11a07938c183d4391de0a76ade31d44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?=
 <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Fri, 24 Nov 2023 21:11:18 +0900
Subject: [PATCH] =?UTF-8?q?enhance(frontend):=20=E3=83=A6=E3=83=BC?=
 =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=AERaw=E3=83=87=E3=83=BC=E3=82=BF?=
 =?UTF-8?q?=E3=82=92=E8=AA=AD=E3=82=81=E3=82=8B=E3=83=9A=E3=83=BC=E3=82=B8?=
 =?UTF-8?q?=E3=82=92=E5=BE=A9=E6=B4=BB=E3=81=95=E3=81=9B=E3=82=8B=20(#1243?=
 =?UTF-8?q?6)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* (add) User raw page

* Update Changelog

* fix lint
---
 CHANGELOG.md                               |   1 +
 packages/frontend/src/pages/user/index.vue |   6 +
 packages/frontend/src/pages/user/raw.vue   | 130 +++++++++++++++++++++
 3 files changed, 137 insertions(+)
 create mode 100644 packages/frontend/src/pages/user/raw.vue

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 64720ef482..0215e7e735 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@
 
 ### Client
 - Enhance: 絵文字のオートコンプリート機能強化 #12364
+- Enhance: ユーザーのRawデータを表示するページが復活
 - fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正
 - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367
 - Fix: コードエディタが正しく表示されない問題を修正
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index 7550d5bcb2..50cc9a3311 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -18,6 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<XPages v-else-if="tab === 'pages'" :user="user"/>
 			<XFlashs v-else-if="tab === 'flashs'" :user="user"/>
 			<XGallery v-else-if="tab === 'gallery'" :user="user"/>
+			<XRaw v-else-if="tab === 'raw'" :user="user"/>
 		</div>
 		<MkError v-else-if="error" @retry="fetchUser()"/>
 		<MkLoading v-else/>
@@ -44,6 +45,7 @@ const XLists = defineAsyncComponent(() => import('./lists.vue'));
 const XPages = defineAsyncComponent(() => import('./pages.vue'));
 const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
 const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
+const XRaw = defineAsyncComponent(() => import('./raw.vue'));
 
 const props = withDefaults(defineProps<{
 	acct: string;
@@ -112,6 +114,10 @@ const headerTabs = $computed(() => user ? [{
 	key: 'gallery',
 	title: i18n.ts.gallery,
 	icon: 'ti ti-icons',
+}, {
+	key: 'raw',
+	title: 'Raw',
+	icon: 'ti ti-code',
 }] : []);
 
 definePageMetadata(computed(() => user ? {
diff --git a/packages/frontend/src/pages/user/raw.vue b/packages/frontend/src/pages/user/raw.vue
new file mode 100644
index 0000000000..0c0bfc29ca
--- /dev/null
+++ b/packages/frontend/src/pages/user/raw.vue
@@ -0,0 +1,130 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkSpacer :contentMax="600" :marginMin="16" :marginMax="32">
+	<div class="_gaps_m">
+		<div :class="$style.userMInfoRoot">
+			<MkAvatar :class="$style.userMInfoAvatar" :user="user" indicator link preview/>
+			<div :class="$style.userMInfoMetaRoot">
+				<span :class="$style.userMInfoMetaName"><MkUserName :class="$style.userMInfoMetaName" :user="user"/></span>
+				<span :class="$style.userMInfoMetaSub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
+				<span :class="$style.userMInfoMetaState">
+					<span v-if="suspended" :class="$style.suspended">Suspended</span>
+					<span v-if="silenced" :class="$style.silenced">Silenced</span>
+					<span v-if="moderator" :class="$style.moderator">Moderator</span>
+				</span>
+			</div>
+		</div>
+
+		<div style="display: flex; flex-direction: column; gap: 1em;">
+			<MkKeyValue :copy="user.id" oneline>
+				<template #key>ID</template>
+				<template #value><span class="_monospace">{{ user.id }}</span></template>
+			</MkKeyValue>
+			<MkKeyValue oneline>
+				<template #key>{{ i18n.ts.createdAt }}</template>
+				<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
+			</MkKeyValue>
+		</div>
+
+		<FormSection>
+			<template #label>Raw</template>
+			<MkObjectView tall :value="user"></MkObjectView>
+		</FormSection>
+	</div>
+</MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as Misskey from 'misskey-js';
+import { acct } from '@/filters/user.js';
+import { i18n } from '@/i18n.js';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import FormSection from '@/components/form/section.vue';
+import MkObjectView from '@/components/MkObjectView.vue';
+
+const props = defineProps<{
+	user: Misskey.entities.User;
+}>();
+
+const moderator = computed(() => props.user.isModerator ?? false);
+const silenced = computed(() => props.user.isSilenced ?? false);
+const suspended = computed(() => props.user.isSuspended ?? false);
+</script>
+
+<style lang="scss" module>
+.userMInfoRoot {
+	display: flex;
+	align-items: center;
+}
+
+.userMInfoAvatar {
+	display: block;
+	width: 64px;
+	height: 64px;
+	margin-right: 16px;
+}
+
+.userMInfoMetaRoot {
+	flex: 1;
+	overflow: hidden;
+}
+
+.userMInfoMetaName {
+	display: block;
+	width: 100%;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.userMInfoMetaSub {
+	display: block;
+	width: 100%;
+	font-size: 85%;
+	opacity: 0.7;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.userMInfoMetaState {
+	display: flex;
+	gap: 8px;
+	flex-wrap: wrap;
+	margin-top: 4px;
+
+	&:empty {
+		display: none;
+	}
+
+	> .suspended,
+	> .silenced,
+	> .moderator {
+		display: inline-block;
+		border: solid 1px;
+		border-radius: 6px;
+		padding: 2px 6px;
+		font-size: 85%;
+	}
+
+	> .suspended {
+		color: var(--error);
+		border-color: var(--error);
+	}
+
+	> .silenced {
+		color: var(--warn);
+		border-color: var(--warn);
+	}
+
+	> .moderator {
+		color: var(--success);
+		border-color: var(--success);
+	}
+}
+</style>