218 lines
5.5 KiB
Vue
218 lines
5.5 KiB
Vue
<script setup lang="ts">
|
|
import { t } from '@/i18n'
|
|
|
|
import { computed, ref } from 'vue'
|
|
import AuthorAvatar from '@/components/AuthorAvatar.vue'
|
|
import AuthorFormPanel from '@/components/AuthorFormPanel.vue'
|
|
import ProfileCard from '@/components/ProfileCard.vue'
|
|
import { useAuthors, type Author, type MyAuthor } from '@/composables/useAuthors'
|
|
import { useMyAuthorProfile } from '@/composables/useMyAuthorProfile'
|
|
import { useLocalizedToast } from '@/i18n/presentation'
|
|
import { baseSlugOf } from '@/lib/authorProfile'
|
|
import { copy } from '@/lib/directoryCopy'
|
|
import { useSessionStore } from '@/stores/session'
|
|
|
|
const SLUG_LOOKUP_SIZE = 25
|
|
|
|
const session = useSessionStore()
|
|
const profile = useMyAuthorProfile()
|
|
const { listAuthors } = useAuthors()
|
|
const toast = useLocalizedToast()
|
|
|
|
const failed = ref(false)
|
|
const created = ref<MyAuthor | null>(null)
|
|
const editing = ref<Author | null>(null)
|
|
const loadingEdit = ref(false)
|
|
|
|
const author = computed(() => session.session?.author)
|
|
const canManage = computed(() => session.canManageContent)
|
|
const nameLine = computed(() => {
|
|
const displayName = session.session?.displayName
|
|
return displayName
|
|
? t('Uses your display name, {0}. You can change it afterwards.', [displayName])
|
|
: t('Uses your username, {0}. You can change it afterwards.', [
|
|
session.session?.username ?? '',
|
|
])
|
|
})
|
|
const adjustedSlug = computed(() => {
|
|
const current = author.value
|
|
const result = created.value
|
|
if (!current || !result || result.id !== current.id) {
|
|
return null
|
|
}
|
|
return result.slug_adjusted ? current.slug : null
|
|
})
|
|
|
|
async function create() {
|
|
failed.value = false
|
|
const outcome = await profile.create()
|
|
if (outcome.kind === 'created') {
|
|
created.value = outcome.author
|
|
}
|
|
failed.value = outcome.kind === 'failed'
|
|
}
|
|
|
|
function announceLoadFailed() {
|
|
toast.add(() => ({
|
|
id: 'author-load-failed',
|
|
color: 'error',
|
|
icon: 'i-lucide-circle-x',
|
|
get title() {
|
|
return copy('loadAuthorsFailed')
|
|
},
|
|
get description() {
|
|
return copy('checkConnection')
|
|
},
|
|
}))
|
|
}
|
|
|
|
async function edit() {
|
|
const current = author.value
|
|
if (!current || loadingEdit.value) {
|
|
return
|
|
}
|
|
loadingEdit.value = true
|
|
try {
|
|
const page = await listAuthors({
|
|
q: current.slug,
|
|
page: 1,
|
|
size: SLUG_LOOKUP_SIZE,
|
|
})
|
|
const full = page.items.find((candidate) => candidate.id === current.id)
|
|
if (full) {
|
|
editing.value = full
|
|
} else {
|
|
announceLoadFailed()
|
|
}
|
|
} catch {
|
|
announceLoadFailed()
|
|
} finally {
|
|
loadingEdit.value = false
|
|
}
|
|
}
|
|
|
|
function closePanel() {
|
|
editing.value = null
|
|
}
|
|
|
|
function onSaved(saved: Author) {
|
|
session.setAuthor({ id: saved.id, name: saved.name, slug: saved.slug })
|
|
closePanel()
|
|
}
|
|
|
|
function onUpdated(updated: Author) {
|
|
if (updated.user_id === null) {
|
|
session.setAuthor(undefined)
|
|
closePanel()
|
|
return
|
|
}
|
|
session.setAuthor({
|
|
id: updated.id,
|
|
name: updated.name,
|
|
slug: updated.slug,
|
|
})
|
|
}
|
|
|
|
function onGone() {
|
|
session.setAuthor(undefined)
|
|
closePanel()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ProfileCard
|
|
:title="t('Author profile')"
|
|
:description="t('Your public byline on articles. It\'s separate from your account.')"
|
|
>
|
|
<template v-if="author">
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<AuthorAvatar :name="author.name" :size="40" />
|
|
<div class="flex min-w-35 flex-1 flex-col">
|
|
<span class="font-medium text-highlighted">{{ author.name }}</span>
|
|
<span class="font-mono text-xs/4.5 text-muted">{{ author.slug }}</span>
|
|
</div>
|
|
<UButton
|
|
color="neutral"
|
|
variant="outline"
|
|
icon="i-lucide-pencil"
|
|
:loading="loadingEdit"
|
|
class="h-11 justify-center border border-accented bg-default px-3 text-highlighted ring-0 lg:h-8"
|
|
@click="edit"
|
|
>
|
|
{{ t('Edit author') }}
|
|
</UButton>
|
|
</div>
|
|
<UAlert
|
|
v-if="adjustedSlug"
|
|
role="status"
|
|
color="info"
|
|
variant="soft"
|
|
icon="i-lucide-info"
|
|
:title="t('Your slug is {0}', [adjustedSlug])"
|
|
:description="
|
|
t('{0} was already used by another author. You can change it in Edit author.', [
|
|
baseSlugOf(adjustedSlug),
|
|
])
|
|
"
|
|
:close="false"
|
|
:ui="{
|
|
root: 'items-start gap-2.5 px-3.5 py-3',
|
|
title: 'font-semibold',
|
|
description: 'text-toned',
|
|
}"
|
|
/>
|
|
<p class="flex items-center gap-1.5 text-xs text-muted">
|
|
<UIcon name="i-lucide-link" class="size-3 shrink-0" />
|
|
{{ t('Linked to your account. New articles start with this author.') }}
|
|
</p>
|
|
</template>
|
|
<template v-else>
|
|
<UAlert
|
|
v-if="failed"
|
|
role="alert"
|
|
color="error"
|
|
variant="soft"
|
|
icon="i-lucide-circle-x"
|
|
:title="t('Couldn\'t create your author profile')"
|
|
:description="t('Try again in a moment.')"
|
|
:close="false"
|
|
:ui="{
|
|
root: 'items-start gap-2.5 px-3.5 py-3',
|
|
title: 'font-semibold',
|
|
description: 'text-toned',
|
|
}"
|
|
/>
|
|
<p class="text-pretty text-toned">
|
|
{{
|
|
t(
|
|
"You don't have an author profile yet. Create one to be credited on your own articles.",
|
|
)
|
|
}}
|
|
</p>
|
|
<div class="flex flex-col gap-3 lg:flex-row lg:items-end">
|
|
<UButton
|
|
icon="i-lucide-feather"
|
|
:loading="profile.pending.value"
|
|
:disabled="profile.pending.value"
|
|
class="h-11 justify-center px-3 lg:h-8"
|
|
@click="create"
|
|
>
|
|
{{ profile.pending.value ? t('Creating…') : t('Create my author profile') }}
|
|
</UButton>
|
|
<span class="text-xs text-muted">{{ nameLine }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<AuthorFormPanel
|
|
v-if="editing"
|
|
:author="editing"
|
|
:own-profile="!canManage"
|
|
:own-email="session.session?.email ?? ''"
|
|
@close="closePanel"
|
|
@saved="onSaved"
|
|
@updated="onUpdated"
|
|
@gone="onGone"
|
|
/>
|
|
</ProfileCard>
|
|
</template>
|