Files
vue-journal-layer/app/journal-pages/TagPage.vue
Jakub Zych 783426c5d8 feat: Journal author and tag pages, SEO, sitemap source and RSS route
- 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
2026-10-09 18:06:20 +02:00

48 lines
2.0 KiB
Vue

<script setup lang="ts">
// journal-tag: posts with one tag, paginated with ?page=N. An unknown tag or
// an out-of-range page is a real 404.
import { pageOutOfRange, parsePageParam } from '../utils/pagination.ts'
import { 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 { t } = useI18n()
const cfg = useRuntimeConfig().public.journal as JournalModuleOptions
const slug = String(route.params.tag ?? '')
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-tag-${slug}-${page}`, async () => {
const tag = (await api.listTags()).find((row) => row.slug === slug)
if (!tag) return null
const posts = await api.listPosts({ tag: tag.slug, page, perPage: cfg.perPage.tag, sort: cfg.sort })
return { tag, 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: 'Tag not found', fatal: true })
if (pageOutOfRange(page, data.value.posts.meta.last_page)) {
throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true })
}
const heading = t('journal.tag.title', { name: data.value.tag.name })
const title = listTitle(heading, page, t('journal.pagination.pageTitle', { page }))
useSeoMeta({ title, ogTitle: title })
</script>
<template>
<section v-if="data" class="journal-tag">
<header class="journal-index-header">
<h1 class="journal-index-header__title">{{ title }}</h1>
</header>
<JournalPostList :posts="data.posts.data" :empty="$t('journal.tag.empty')" />
<JournalPagination :current="data.posts.meta.current_page" :last="data.posts.meta.last_page" />
</section>
</template>