Files
vue-journal-layer/app/journal-pages/PostPage.vue
Jakub Zych c408eeb2ca feat: Journal list, category pages, pagination, tokens and pl/en strings
- index and category pages paginate with ?page=N and 404 out of range
- category content renders above the list through the html gate
- related posts fall back to the latest posts
- --journal-* tokens and neutral base styles
- pl/en strings registered only for locales the app enables
2026-10-09 18:02:48 +02:00

68 lines
2.6 KiB
Vue

<script setup lang="ts">
// journal-post: one published post by slug. An unknown, draft or future post
// is a real 404. The transform gates the body before it reaches the payload.
// Related posts fall back to the latest posts when the API has none (it
// returns none for a post without a category).
import { toListPost, toPostView } from '../utils/view.ts'
import type { JournalModuleOptions } from '../utils/types.ts'
const route = useRoute()
const api = useJournalApi()
const links = useJournalLinks()
const cfg = useRuntimeConfig().public.journal as JournalModuleOptions
const slug = computed(() => String(route.params.slug ?? ''))
const { data: post, error } = await useAsyncData(
() => `journal-post-${slug.value}`,
async () => {
const found = await api.getPost(slug.value)
if (!found) return null
const count = Math.max(0, cfg.relatedCount)
let related = found.related_posts ?? []
if (related.length === 0 && count > 0) {
const latest = await api.listPosts({ perPage: count + 1, sort: cfg.sort })
related = latest.data.filter((p) => p.slug !== found.slug)
}
return { ...found, related_posts: related.slice(0, count).map(toListPost) }
},
{ transform: (p) => (p ? toPostView(p) : null) },
)
if (error.value) {
throw createError({ statusCode: (error.value as { statusCode?: number }).statusCode ?? 500, fatal: true })
}
if (!post.value) {
throw createError({ statusCode: 404, statusMessage: 'Post not found', fatal: true })
}
</script>
<template>
<article v-if="post" class="journal-post">
<header class="journal-post__header">
<h1 class="journal-post__title">{{ post.title }}</h1>
<JournalPostMeta :post="post" />
</header>
<JournalFeaturedImage :src="post.featured_image_url" :alt="post.title" eager />
<JournalPostBody :html="post.content_html" :blocked="post.content_blocked" />
<nav v-if="post.previous_post || post.next_post" class="journal-post__siblings">
<NuxtLink
v-if="post.previous_post && links.postLink(post.previous_post.slug)"
:to="links.postLink(post.previous_post.slug)!"
rel="prev"
class="journal-post__previous"
>
<span>{{ $t('journal.post.previous') }}</span> {{ post.previous_post.title }}
</NuxtLink>
<NuxtLink
v-if="post.next_post && links.postLink(post.next_post.slug)"
:to="links.postLink(post.next_post.slug)!"
rel="next"
class="journal-post__next"
>
<span>{{ $t('journal.post.next') }}</span> {{ post.next_post.title }}
</NuxtLink>
</nav>
<JournalRelatedPosts :posts="post.related_posts" />
</article>
</template>