95 lines
3.2 KiB
Vue
95 lines
3.2 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, watch, computed } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useBlog, type BlogPost } from '../composables/useBlog';
|
|
import { useSEO } from '../composables/useSEO';
|
|
import { useStructuredData } from '../composables/useStructuredData';
|
|
import { marked } from 'marked';
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const { getPost } = useBlog();
|
|
const { addBlogPostSchema, addBreadcrumbSchema } = useStructuredData();
|
|
const post = ref<BlogPost | undefined>(undefined);
|
|
const renderedContent = ref('');
|
|
|
|
const slug = computed(() => route.params.slug as string);
|
|
|
|
// Initialize with default SEO (will be updated when post loads)
|
|
useSEO({
|
|
title: 'Blog Post',
|
|
description: 'Read our latest article about spritesheet generation and game development.',
|
|
url: `/blog/${slug.value}`,
|
|
type: 'article',
|
|
keywords: 'sprite sheet, game development, blog'
|
|
});
|
|
|
|
addBreadcrumbSchema([
|
|
{ name: 'Home', url: '/' },
|
|
{ name: 'Blog', url: '/blog' },
|
|
{ name: 'Article', url: `/blog/${slug.value}` }
|
|
]);
|
|
|
|
onMounted(async () => {
|
|
post.value = await getPost(slug.value);
|
|
|
|
if (post.value) {
|
|
renderedContent.value = await marked(post.value.content);
|
|
} else {
|
|
router.push({ name: 'blog-overview' });
|
|
}
|
|
});
|
|
|
|
// Update SEO and structured data when post loads
|
|
watch(post, (newPost) => {
|
|
if (newPost) {
|
|
useSEO({
|
|
title: newPost.title,
|
|
description: newPost.description,
|
|
image: newPost.image,
|
|
url: `/blog/${slug.value}`,
|
|
type: 'article',
|
|
author: newPost.author || 'nu11ed',
|
|
publishedTime: newPost.date,
|
|
keywords: newPost.keywords || 'sprite sheet, game development, blog'
|
|
});
|
|
|
|
addBlogPostSchema({
|
|
title: newPost.title,
|
|
description: newPost.description,
|
|
author: newPost.author || 'nu11ed',
|
|
datePublished: newPost.date,
|
|
image: newPost.image,
|
|
url: `/blog/${slug.value}`
|
|
});
|
|
|
|
addBreadcrumbSchema([
|
|
{ name: 'Home', url: '/' },
|
|
{ name: 'Blog', url: '/blog' },
|
|
{ name: newPost.title, url: `/blog/${slug.value}` }
|
|
]);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="w-full">
|
|
<div v-if="post">
|
|
<RouterLink :to="{ name: 'blog-overview' }" class="inline-flex items-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-6 transition-colors" title="Return to blog overview" aria-label="Navigate back to blog overview page">
|
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
</svg>
|
|
Back to overview
|
|
</RouterLink>
|
|
|
|
<h1 class="text-4xl font-bold mb-4 text-gray-900 dark:text-white">{{ post.title }}</h1>
|
|
<p class="text-gray-600 dark:text-gray-400 text-sm mb-8">{{ post.date }}</p>
|
|
<!-- Image is intentionally omitted here as per requirements -->
|
|
<div class="prose max-w-none" v-html="renderedContent"></div>
|
|
</div>
|
|
<div v-else class="text-center py-12">
|
|
<p class="text-xl text-gray-600 dark:text-gray-400">Loading...</p>
|
|
</div>
|
|
</div>
|
|
</template>
|