291 lines
8.8 KiB
Vue
291 lines
8.8 KiB
Vue
<script setup lang="ts">
|
|
import type { Actor } from '~/types'
|
|
|
|
import { onBeforeRouteUpdate } from 'vue-router'
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useStore } from '~/store'
|
|
import UserFollowButton from '~/components/federation/UserFollowButton.vue'
|
|
import axios from 'axios'
|
|
|
|
import useErrorHandler from '~/composables/useErrorHandler'
|
|
import useReport from '~/composables/moderation/useReport'
|
|
import RenderedDescription from '~/components/common/RenderedDescription.vue'
|
|
|
|
import Layout from '~/components/ui/Layout.vue'
|
|
import Section from '~/components/ui/Section.vue'
|
|
import Spacer from '~/components/ui/Spacer.vue'
|
|
import Link from '~/components/ui/Link.vue'
|
|
import Tabs from '~/components/ui/Tabs.vue'
|
|
import Tab from '~/components/ui/Tab.vue'
|
|
|
|
interface Events {
|
|
(e: 'updated', value: Actor): void
|
|
}
|
|
|
|
interface Props {
|
|
username: string
|
|
domain?: string | null
|
|
}
|
|
|
|
const emit = defineEmits<Events>()
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
domain: null
|
|
})
|
|
|
|
const { report, getReportableObjects } = useReport()
|
|
const store = useStore()
|
|
|
|
// We are working either with an Actor or null
|
|
const object = ref<Actor | null>(null)
|
|
|
|
const displayName = computed(() => object.value?.name ?? object.value?.preferred_username)
|
|
const fullUsername = computed(() => props.domain
|
|
? `${props.username}@${props.domain}`
|
|
: `${props.username}@${store.getters['instance/domain']}`
|
|
)
|
|
|
|
const routerParams = computed(() => props.domain
|
|
? { username: props.username, domain: props.domain }
|
|
: { username: props.username }
|
|
)
|
|
|
|
const { t } = useI18n()
|
|
const labels = computed(() => ({
|
|
usernameProfile: t('views.auth.ProfileBase.title', { username: props.username })
|
|
}))
|
|
|
|
onBeforeRouteUpdate((to) => {
|
|
to.meta.preserveScrollPosition = true
|
|
})
|
|
|
|
const isLoading = ref(false)
|
|
const fetchData = async () => {
|
|
object.value = null
|
|
isLoading.value = true
|
|
|
|
try {
|
|
const response = await axios.get(`federation/actors/${fullUsername.value}/`)
|
|
object.value = response.data
|
|
} catch (error) {
|
|
useErrorHandler(error as Error)
|
|
}
|
|
|
|
isLoading.value = false
|
|
}
|
|
|
|
watch(props, fetchData, { immediate: true })
|
|
</script>
|
|
|
|
<template>
|
|
<Layout stack main
|
|
v-title="labels.usernameProfile"
|
|
>
|
|
<Layout flex>
|
|
<!-- Profile Picture -->
|
|
<i
|
|
v-if="!object?.icon"
|
|
class="avatar icon"
|
|
>
|
|
<!-- TODO: Get User Initials -->
|
|
TU
|
|
</i>
|
|
<img
|
|
v-else
|
|
v-lazy="store.getters['instance/absoluteUrl'](object.icon.urls.medium_square_crop)"
|
|
alt=""
|
|
class="avatar"
|
|
/>
|
|
<!-- <div class="ui five wide column">
|
|
<button
|
|
ref="dropdown"
|
|
v-dropdown="{direction: 'downward'}"
|
|
class="ui pointing dropdown icon small basic right floated button"
|
|
style="position: absolute; right: 1em; top: 1em;"
|
|
>
|
|
<i class="ellipsis vertical icon" />
|
|
<div class="menu">
|
|
<a
|
|
v-if="object.domain != store.getters['instance/domain']"
|
|
:href="object.fid"
|
|
target="_blank"
|
|
class="basic item"
|
|
>
|
|
<i class="external icon" />
|
|
{{ t('views.auth.ProfileBase.link.domainView', {domain: object.domain}) }}
|
|
</a>
|
|
<div
|
|
v-for="obj in getReportableObjects({account: object})"
|
|
:key="obj.target.type + obj.target.id"
|
|
role="button"
|
|
class="basic item"
|
|
@click.stop.prevent="report(obj)"
|
|
>
|
|
<i class="share icon" /> {{ obj.label }}
|
|
</div>
|
|
|
|
<div class="divider" />
|
|
<router-link
|
|
v-if="store.state.auth.availablePermissions['moderation']"
|
|
class="basic item"
|
|
:to="{name: 'manage.moderation.accounts.detail', params: {id: object.full_username}}"
|
|
>
|
|
<i class="wrench icon" />
|
|
{{ t('views.auth.ProfileBase.link.moderation') }}
|
|
</router-link>
|
|
</div>
|
|
</button>
|
|
<user-follow-button
|
|
v-if="store.state.auth.authenticated && object && object.full_username !== store.state.auth.fullUsername"
|
|
:actor="object"
|
|
/>
|
|
<h1 class="ui center aligned icon header">
|
|
<i
|
|
v-if="!object.icon"
|
|
class="circular inverted user success icon"
|
|
/>
|
|
<img
|
|
v-else
|
|
v-lazy="store.getters['instance/absoluteUrl'](object.icon.urls.medium_square_crop)"
|
|
alt=""
|
|
class="ui big circular image"
|
|
>
|
|
<div class="ellispsis content">
|
|
<div class="ui very small hidden divider" />
|
|
<span>{{ displayName }}</span>
|
|
<div class="ui very small hidden divider" />
|
|
<div
|
|
class="sub header ellipsis"
|
|
:title="object.full_username"
|
|
>
|
|
{{ object.full_username }}
|
|
</div>
|
|
</div>
|
|
<template v-if="object.full_username === store.state.auth.fullUsername">
|
|
<div class="ui very small hidden divider" />
|
|
<div class="ui basic success label">
|
|
{{ t('views.auth.ProfileBase.label.self') }}
|
|
</div>
|
|
</template>
|
|
</h1>
|
|
<div class="ui small hidden divider" />
|
|
<div v-if="store.getters['ui/layoutVersion'] === 'large'">
|
|
<rendered-description
|
|
:content="object.summary"
|
|
:field-name="'summary'"
|
|
:update-url="`users/${store.state.auth.username}/`"
|
|
:can-update="store.state.auth.authenticated && object.full_username === store.state.auth.fullUsername"
|
|
@updated="emit('updated', $event)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="ui eleven wide column">
|
|
<div class="ui head vertical stripe segment">
|
|
<div class="ui container">
|
|
<div class="ui secondary pointing center aligned menu">
|
|
<router-link
|
|
class="item"
|
|
:to="{name: 'profile.overview', params: routerParams}"
|
|
>
|
|
{{ t('views.auth.ProfileBase.link.overview') }}
|
|
</router-link>
|
|
<router-link
|
|
class="item"
|
|
:to="{name: 'profile.activity', params: routerParams}"
|
|
>
|
|
{{ t('views.auth.ProfileBase.link.activity') }}
|
|
</router-link>
|
|
</div>
|
|
<div class="ui hidden divider" />
|
|
<router-view
|
|
:object="object"
|
|
@updated="fetchData"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div> -->
|
|
<!-- Profile description -->
|
|
<Section no-items
|
|
:h1="store.state.auth.username"
|
|
:action="{ text:'Edit profile', to:'/settings' }"
|
|
style="flex-grow: 1; margin-top: 58px;"
|
|
>
|
|
<span style="grid-column: 1 / -1">
|
|
{{ object?.full_username }}
|
|
<i class="bi bi-copy" style="margin-left: 8px;"/>
|
|
</span>
|
|
<RenderedDescription
|
|
style="grid-column: 1 / -1"
|
|
:content="object?.summary? {text: object.summary} : null"
|
|
:field-name="'summary'"
|
|
:update-url="`users/${store.state.auth.username}/`"
|
|
:can-update="store.state.auth.authenticated && object?.full_username === store.state.auth.fullUsername"
|
|
@updated="emit('updated', $event)"
|
|
/>
|
|
<UserFollowButton
|
|
v-if="store.state.auth.authenticated && object && object.full_username !== store.state.auth.fullUsername"
|
|
:actor="object"
|
|
/>
|
|
</Section>
|
|
</Layout>
|
|
<!-- TODO: Make routerlinks work for tabs -->
|
|
<Tabs>
|
|
<Tab title="Overview" :to="{name: 'profile.overview', params: routerParams}">
|
|
{{ t('views.auth.ProfileBase.link.overview') }}
|
|
<router-view
|
|
:object="object"
|
|
@updated="fetchData"
|
|
/>
|
|
</Tab>
|
|
|
|
<Tab title="Collections">
|
|
{{ t('views.auth.ProfileBase.link.collections') }}
|
|
</Tab>
|
|
|
|
<Tab title="Channels">
|
|
{{ t('views.auth.ProfileBase.link.channels') }}
|
|
</Tab>
|
|
|
|
<Tab title="Activity" :to="{name: 'profile.activity', params: routerParams}">
|
|
{{ t('views.auth.ProfileBase.link.activity') }}
|
|
<router-view
|
|
:object="object"
|
|
@updated="fetchData"
|
|
/>
|
|
</Tab>
|
|
</Tabs>
|
|
</Layout>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
img.avatar {
|
|
width: 176px;
|
|
height: 176px;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
i.avatar {
|
|
font-size: 100px;
|
|
font-style: normal;
|
|
font-weight: 800;
|
|
padding: 26px 0px 0px 26px;
|
|
background-color: var(--fw-gray-500);
|
|
border-radius: 50%;
|
|
width: 176px;
|
|
height: 176px;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 48px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
a.edit span {
|
|
color: var(--fw-primary);
|
|
|
|
&:hover {
|
|
color: var(--color);
|
|
}
|
|
}
|
|
</style>
|