- author pages resolve slug or login and canonicalise to the slug - tag pages, per-page SEO meta and canonicals that keep ?page=N - sitemap source pages the API and builds locs from journal.routes - /rss.xml proxies the API feed with x-forwarded-for - neutral README; version 0.1.0
60 lines
2.6 KiB
Vue
60 lines
2.6 KiB
Vue
<script setup lang="ts">
|
|
// journal-author: an author and their published posts, paginated with
|
|
// ?page=N. {author} is the author slug, or a login with published posts. An
|
|
// unknown author or an out-of-range page is a real 404. When the visitor came
|
|
// in by login, the canonical points at the slug URL.
|
|
import { pageOutOfRange, parsePageParam } from '../utils/pagination.ts'
|
|
import { absoluteUrl, listTitle } from '../utils/seo.ts'
|
|
import { toListPost } from '../utils/view.ts'
|
|
import type { JournalModuleOptions } from '../utils/types.ts'
|
|
|
|
definePageMeta({ key: (route) => route.fullPath })
|
|
|
|
const route = useRoute()
|
|
const api = useJournalApi()
|
|
const links = useJournalLinks()
|
|
const site = useSiteConfig()
|
|
const { t } = useI18n()
|
|
const cfg = useRuntimeConfig().public.journal as JournalModuleOptions
|
|
const param = String(route.params.author ?? '')
|
|
|
|
const page = parsePageParam(route.query.page)
|
|
if (page === null) throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
|
|
const { data, error } = await useAsyncData(`journal-author-${param}-${page}`, async () => {
|
|
const author = await api.getAuthor(param)
|
|
if (!author) return null
|
|
const posts = await api.listAuthorPosts(param, { page, perPage: cfg.perPage.author, sort: cfg.sort })
|
|
if (!posts) return null
|
|
return { author, posts: { ...posts, data: posts.data.map(toListPost) } }
|
|
})
|
|
if (error.value) {
|
|
throw createError({ statusCode: (error.value as { statusCode?: number }).statusCode ?? 500, fatal: true })
|
|
}
|
|
if (!data.value) throw createError({ statusCode: 404, statusMessage: 'Author not found', fatal: true })
|
|
if (pageOutOfRange(page, data.value.posts.meta.last_page)) {
|
|
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
|
|
}
|
|
|
|
const author = data.value.author
|
|
const name = `${author.first_name} ${author.last_name}`.trim() || author.slug
|
|
const title = listTitle(t('journal.author.title', { name }), page, t('journal.pagination.pageTitle', { page }))
|
|
useSeoMeta({ title, ogTitle: title, ogType: 'profile' })
|
|
|
|
if (author.slug !== param) {
|
|
const path = links.authorLink(author.slug)
|
|
if (path) {
|
|
const href = absoluteUrl(page > 1 ? `${path}?page=${page}` : path, site.url || useRequestURL().origin)
|
|
useHead({ link: [{ rel: 'canonical', href, key: 'canonical' }] })
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section v-if="data" class="journal-author">
|
|
<JournalAuthorCard :author="data.author" :page="page" />
|
|
<JournalPostList :posts="data.posts.data" :empty="$t('journal.author.empty')" />
|
|
<JournalPagination :current="data.posts.meta.current_page" :last="data.posts.meta.last_page" />
|
|
</section>
|
|
</template>
|