NuxtJS, Vue, real web app Nuxt content: blog Better mobile/responsive design and practices Better practices overall
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
const slug = route.params.slug
|
||||
const { data } = await useAsyncData(() => queryCollection('blog').path("/blog/" + slug).first())
|
||||
const title = (data.value == undefined ? 'Blog' : data.value!.title);
|
||||
|
||||
useSeoMeta({
|
||||
title: data.value?.title,
|
||||
description: data.value?.description
|
||||
})
|
||||
defineOgImageComponent('BlogPost', {
|
||||
title: title,
|
||||
description: data.value?.description
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HeaderCompact :title="title" previous-link="/blog" />
|
||||
|
||||
<div class="flex xl:flex-row flex-col-reverse xl:mt-20 sm:mt-10 mt-5 xl:mb-20 mb-5 m-auto">
|
||||
<ContentRenderer v-if="data" :value="data" class="max-w-4xl w-[90%] xl:mr-10 m-auto content" />
|
||||
|
||||
<div v-if="data?.body.toc != undefined" class="xl:visible collapse xl:mt-0 mt-5 xl:ml-10 ml-5 mb-10 m-auto max-w-60 xl:sticky xl:top-16">
|
||||
<ul>
|
||||
<BlogTocLink v-for="link of data.body.toc.links" :link="link" />
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.content h2 {
|
||||
font-size: 30px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content h3 {
|
||||
font-size: 26px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content h4 {
|
||||
font-size: 23px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content h5 {
|
||||
font-size: 21px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content p {
|
||||
font-size: 20px;
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.content ul {
|
||||
list-style-type: disc;
|
||||
list-style-position: inside;
|
||||
font-size: 20px;
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.content li {
|
||||
list-style-type: disc;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
.content a {
|
||||
color: initial;
|
||||
}
|
||||
.content a:link {
|
||||
color: initial;
|
||||
}
|
||||
.content a:visited {
|
||||
color: initial;
|
||||
}
|
||||
.content a:hover {
|
||||
color: initial;
|
||||
}
|
||||
.content a:active {
|
||||
color: initial;
|
||||
}
|
||||
.content p a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
defineOgImageComponent('Site', {
|
||||
title: 'Blog'
|
||||
})
|
||||
|
||||
const content = await queryCollection('blog').order('date', 'DESC').all()
|
||||
|
||||
function formatDateString(dateString: string) : string {
|
||||
let date = new Date(Date.parse(dateString))
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
dateStyle: "long",
|
||||
timeZone: "Europe/Paris",
|
||||
}).format(date)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HeaderCompact title="Blog" previous-link="/" class="mb-20" />
|
||||
|
||||
<div class="m-auto lg:w-[56rem] w-[90%]" v-for="article in content">
|
||||
<h1 class="text-2xl" v-if="article">{{ article.title }}</h1>
|
||||
|
||||
<h3 class="text-xl" v-if="article">
|
||||
{{ formatDateString(article.date) }}
|
||||
</h3>
|
||||
|
||||
<div class="flex">
|
||||
<h4 v-for="tag in article.tags"
|
||||
class="p-1 bg-slate-100 mr-3 hover:cursor-pointer">
|
||||
{{ tag }}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<h4 class="text-xl mt-2" v-if="article">{{ article.description }}</h4>
|
||||
|
||||
<a :href="article.path">
|
||||
<h2 class="text-xl text-right">[ Read more ]</h2>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user