This repository has been archived on 2024-06-08. You can view files and clone it, but cannot push or open issues or pull requests.
2024-04-04 18:35:48 +08:00

203 lines
5.7 KiB
Vue

<template>
<v-container class="wrapper h-auto no-scrollbar">
<div class="min-w-0 name-card md:col-span-2 max-md:order-first">
<v-card class="w-full">
<v-img v-if="accountBanner" cover max-height="280px" :aspect-ratio="16 / 9" :src="accountBanner" />
<v-card-text class="flex px-5 gap-1">
<v-avatar
color="grey-lighten-2"
icon="mdi-account-circle"
class="rounded-card me-2"
:image="accountPicture ?? ''"
/>
<div>
<div class="flex items-center gap-1">
<h2 class="text-lg font-medium">{{ metadata?.nick }}</h2>
<span class="text-sm opacity-80">@{{ metadata?.name }}</span>
</div>
<p v-if="metadata?.description" class="mt-[-4px]">{{ metadata?.description }}</p>
<p v-else class="mt-[-4px] italic">No description yet.</p>
</div>
</v-card-text>
</v-card>
</div>
<v-card class="min-w-0 browser h-fit">
<v-tabs v-model="tab" align-tabs="center" bg-color="grey-lighten-4">
<v-tab value="page">Personal Page</v-tab>
<v-tab value="timeline">Timeline</v-tab>
</v-tabs>
<v-card-text class="content">
<v-window v-model="tab" content-class="px-5">
<v-window-item value="page">
<div class="px-3">
<article v-if="page?.content" class="prose max-w-none" v-html="parseContent(page?.content)" />
<article v-else>
<v-alert variant="tonal" type="info">
The user didn't customize its personal page.
</v-alert>
</article>
</div>
</v-window-item>
<v-window-item value="timeline">
<post-list class="mt-[-16px]" variant="outlined" :loader="readMore" v-model:posts="posts" />
</v-window-item>
</v-window>
</v-card-text>
</v-card>
<div class="min-w-0 aside h-fit max-md:order-first">
<v-card prepend-icon="mdi-account-details" title="Bio">
<v-card-text class="flex flex-col gap-2.5">
<div>
<h3 class="font-bold">Power level</h3>
<v-chip :color="parsePowerLevel(metadata?.power_level).color" size="small">
<span>{{ parsePowerLevel(metadata?.power_level).title }}</span>
<span>&nbsp;·&nbsp;</span>
<span class="font-mono">{{ metadata?.power_level }}</span>
</v-chip>
</div>
<div>
<h3 class="font-bold">Joined at</h3>
<p>{{ new Date(metadata?.created_at).toLocaleString() }}</p>
</div>
<div>
<h3 class="font-bold">UID</h3>
<p class="text-mono opacity-90">#{{ metadata?.id.toString().padStart(12, "0") }}</p>
</div>
</v-card-text>
</v-card>
</div>
</v-container>
</template>
<script setup lang="ts">
import { computed, reactive, ref } from "vue"
import { buildRequestUrl, request } from "@/scripts/request"
import { useRoute } from "vue-router"
import PostList from "@/components/posts/PostList.vue"
import { parse } from "marked"
import Articles from "@/views/posts/articles.vue"
const route = useRoute()
const error = ref<null | string>(null)
const loading = ref(false)
const tab = ref("page")
const pagination = reactive({ page: 1, pageSize: 5, total: 0 })
const metadata = ref<any>(null)
const page = ref<any>(null)
const posts = ref<any[]>([])
const accountPicture = computed(() => metadata.value?.avatar ?
buildRequestUrl("identity", `/api/avatar/${metadata.value?.avatar}`) :
null
)
const accountBanner = computed(() => metadata.value?.banner ?
buildRequestUrl("identity", `/api/avatar/${metadata.value?.banner}`) :
null
)
async function readMetadata() {
loading.value = true
const res = await request("identity", `/api/users/${route.params.alias}`)
if (res.status !== 200) {
error.value = await res.text()
} else {
metadata.value = await res.json()
}
loading.value = false
}
async function readPage() {
loading.value = true
const res = await request("identity", `/api/users/${route.params.alias}/page`)
if (res.status !== 200) {
error.value = await res.text()
} else {
page.value = await res.json()
}
loading.value = false
}
async function readPosts() {
const res = await request(
"interactive",
`/api/feed?` +
new URLSearchParams({
take: pagination.pageSize.toString(),
offset: ((pagination.page - 1) * pagination.pageSize).toString(),
authorId: route.params.alias as string
})
)
if (res.status !== 200) {
error.value = await res.text()
} else {
error.value = null
const data = await res.json()
pagination.total = data["count"]
posts.value.push(...(data["data"] ?? []))
}
}
async function readMore({ done }: any) {
// Reach the end of data
if (pagination.total <= pagination.page * pagination.pageSize) {
done("empty")
return
}
pagination.page++
await readPosts()
if (error.value != null) done("error")
else {
if (pagination.total > 0) done("ok")
else done("empty")
}
}
Promise.all([readMetadata(), readPage(), readPosts()])
function parsePowerLevel(level: number): { color: string, title: string } {
if (level < 50) {
return { color: "green", title: "User" }
} else if (level < 100) {
return { color: "orange", title: "Moderator" }
} else {
return { color: "red", title: "Administrator" }
}
}
function parseContent(src: string): string {
return parse(src) as string
}
</script>
<style scoped>
.wrapper {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 0.75rem;
}
@media (max-width: 768px) {
.wrapper {
grid-template-columns: 1fr;
}
}
.rounded-card {
border-radius: 8px;
}
</style>