feat(ui) [WIP] User profile page
This commit is contained in:
parent
0d5d3bbba1
commit
601cc3a663
|
@ -261,10 +261,6 @@ const changeEmail = async () => {
|
|||
isChangingEmail.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
$('select.dropdown').dropdown()
|
||||
})
|
||||
|
||||
fetchApps()
|
||||
fetchOwnedApps()
|
||||
</script>
|
||||
|
|
|
@ -6,6 +6,9 @@ import { useI18n } from 'vue-i18n'
|
|||
|
||||
import useLogger from '~/composables/useLogger'
|
||||
|
||||
import Textarea from '~/components/ui/Textarea.vue'
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
interface Events {
|
||||
(e: 'update:modelValue', value: string): void
|
||||
}
|
||||
|
@ -83,22 +86,20 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content-form ui segments">
|
||||
<div class="ui segment">
|
||||
<div class="ui tiny secondary pointing menu">
|
||||
<button
|
||||
:class="[{active: !isPreviewing}, 'item']"
|
||||
<Button
|
||||
@click.prevent="isPreviewing = false"
|
||||
:class="[{active: !isPreviewing}, 'item']"
|
||||
title="write"
|
||||
>
|
||||
{{ t('components.common.ContentForm.button.write') }}
|
||||
</button>
|
||||
<button
|
||||
:class="[{active: isPreviewing}, 'item']"
|
||||
</Button>
|
||||
<Button
|
||||
@click.prevent="isPreviewing = true"
|
||||
:class="[{active: isPreviewing}, 'item']"
|
||||
title="preview"
|
||||
>
|
||||
{{ t('components.common.ContentForm.button.preview') }}
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
<template v-if="isPreviewing">
|
||||
<div
|
||||
v-if="isLoadingPreview"
|
||||
|
@ -121,17 +122,14 @@ onMounted(async () => {
|
|||
</template>
|
||||
<template v-else>
|
||||
<div class="ui transparent input">
|
||||
<textarea
|
||||
<Textarea
|
||||
ref="textarea"
|
||||
v-model="value"
|
||||
:required="required"
|
||||
:placeholder="labels.placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div class="ui very small hidden divider" />
|
||||
</template>
|
||||
</div>
|
||||
<div class="ui bottom attached segment">
|
||||
<span
|
||||
v-if="charLimit"
|
||||
:class="['right', 'floated', {'ui danger text': remainingChars < 0}]"
|
||||
|
@ -141,6 +139,4 @@ onMounted(async () => {
|
|||
<p>
|
||||
{{ t('components.common.ContentForm.help.markdown') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -8,6 +8,9 @@ import { useI18n } from 'vue-i18n'
|
|||
import axios from 'axios'
|
||||
import clip from 'text-clipper'
|
||||
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
import Alert from '~/components/ui/Alert.vue'
|
||||
|
||||
interface Events {
|
||||
(e: 'updated', data: unknown): void
|
||||
}
|
||||
|
@ -88,9 +91,10 @@ const submit = async () => {
|
|||
<template v-if="content && !isUpdating">
|
||||
<sanitized-html :html="html" />
|
||||
<template v-if="isTruncated">
|
||||
<div class="ui small hidden divider" />
|
||||
<a
|
||||
v-if="showMore === false"
|
||||
class="more"
|
||||
style="float: right; margin-top: -32px; color: var(--fw-primary);"
|
||||
href=""
|
||||
@click.stop.prevent="showMore = true"
|
||||
>
|
||||
|
@ -98,6 +102,8 @@ const submit = async () => {
|
|||
</a>
|
||||
<a
|
||||
v-else
|
||||
class="more"
|
||||
style="float: right; margin-top: -32px; color: var(--fw-primary);"
|
||||
href=""
|
||||
@click.stop.prevent="showMore = false"
|
||||
>
|
||||
|
@ -109,28 +115,23 @@ const submit = async () => {
|
|||
{{ t('components.common.RenderedDescription.empty.noDescription') }}
|
||||
</p>
|
||||
<template v-if="!isUpdating && canUpdate && updateUrl">
|
||||
<div class="ui hidden divider" />
|
||||
<span
|
||||
<Button
|
||||
role="button"
|
||||
icon="bi-pencil"
|
||||
@click="isUpdating = true"
|
||||
>
|
||||
<i class="pencil icon" />
|
||||
{{ t('components.common.RenderedDescription.button.edit') }}
|
||||
</span>
|
||||
</Button>
|
||||
</template>
|
||||
<form
|
||||
v-if="isUpdating"
|
||||
class="ui form"
|
||||
@submit.prevent="submit()"
|
||||
>
|
||||
<div
|
||||
<Alert red
|
||||
v-if="errors.length > 0"
|
||||
title="{{ t('components.common.RenderedDescription.header.failure') }}"
|
||||
role="alert"
|
||||
class="ui negative message"
|
||||
>
|
||||
<h4 class="header">
|
||||
{{ t('components.common.RenderedDescription.header.failure') }}
|
||||
</h4>
|
||||
<ul class="list">
|
||||
<li
|
||||
v-for="(error, key) in errors"
|
||||
|
@ -139,25 +140,28 @@ const submit = async () => {
|
|||
{{ error }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Alert>
|
||||
<content-form
|
||||
v-model="text"
|
||||
:autofocus="true"
|
||||
/>
|
||||
<a
|
||||
<Button
|
||||
class="left floated"
|
||||
@click.prevent="isUpdating = false"
|
||||
solid
|
||||
secondary
|
||||
>
|
||||
{{ t('components.common.RenderedDescription.button.cancel') }}
|
||||
</a>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
|
||||
type="submit"
|
||||
:disabled="isLoading"
|
||||
solid
|
||||
primary
|
||||
>
|
||||
{{ t('components.common.RenderedDescription.button.update') }}
|
||||
</button>
|
||||
<div class="ui clearing hidden divider" />
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -64,6 +64,11 @@ onMounted(() => {
|
|||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
|
||||
.force-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link {
|
||||
|
||||
// Layout
|
||||
|
|
|
@ -12,7 +12,11 @@ import useErrorHandler from '~/composables/useErrorHandler'
|
|||
import useReport from '~/composables/moderation/useReport'
|
||||
|
||||
import Layout from '~/components/ui/Layout.vue'
|
||||
import Section from '~/components/ui/layout/Section.vue'
|
||||
import Spacer from '~/components/ui/layout/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
|
||||
|
@ -76,126 +80,163 @@ watch(props, fetchData, { immediate: true })
|
|||
v-title="labels.usernameProfile"
|
||||
class="page-profile"
|
||||
>
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="ui vertical segment"
|
||||
>
|
||||
<div class="ui centered active inline loader" />
|
||||
</div>
|
||||
<div class="ui head vertical stripe segment container">
|
||||
<div
|
||||
v-if="object"
|
||||
class="ui stackable grid"
|
||||
>
|
||||
<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"
|
||||
<Layout flex>
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"
|
||||
alt="{{ displayName }}"
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
<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 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>
|
||||
</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"
|
||||
/>
|
||||
</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>
|
||||
<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> -->
|
||||
</Layout>
|
||||
<Layout stack noGap style="flex-grow: 2;">
|
||||
<Layout flex style="justify-content: space-between;">
|
||||
<!-- <Section h1="{{ displayName }}" :action="{ text: 'Edit profile', to:'/settings' }" /> -->
|
||||
<h1>{{ displayName }}</h1>
|
||||
<Link ghost alignSelf="center" minContent forceUnderline primary style="color: var(--fw-primary);" to="/settings">Edit profile</Link>
|
||||
</Layout>
|
||||
<span>{{ object?.full_username }}<i class="bi bi-copy" style="margin-left: 8px;"/></span>
|
||||
<rendered-description
|
||||
:content="object?.summary"
|
||||
:field-name="'info'"
|
||||
:update-url="`users/${store.state.auth.username}/`"
|
||||
:can-update="store.state.auth.authenticated && object?.full_username === store.state.auth.fullUsername"
|
||||
@updated="emit('updated', $event)"
|
||||
/>
|
||||
</Layout>
|
||||
</Layout>
|
||||
<Tabs>
|
||||
<Tab title="Overview">{{ t('views.auth.ProfileBase.link.overview') }}</Tab>
|
||||
<Tab title="Collections">{{ t('views.auth.ProfileBase.link.collections') }}</Tab>
|
||||
<Tab title="Channels">{{ t('views.auth.ProfileBase.link.channels') }}</Tab>
|
||||
<Tab title="Activity">{{ t('views.auth.ProfileBase.link.activity') }}</Tab>
|
||||
</Tabs>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
img.avatar {
|
||||
width: 176px;
|
||||
height: 176px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
a.edit span {
|
||||
color: var(--fw-primary);
|
||||
|
||||
&:hover {
|
||||
color: var(--color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue