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>