1<script setup lang="ts"> 2const config = useRuntimeConfig().public; 3 4const { data: frontmatter } = await useAsyncData("frontmatter", () => 5 queryCollection("pages").path("/pages").first() 6); 7 8const { data } = await useAsyncData("postList", () => { 9 return queryCollectionNavigation("posts", [ 10 "path", 11 "title", 12 "date", 13 "description", 14 "authors", 15 "tags" 16 ]) 17 .where("published", "<>", false) 18 .order("date", "DESC"); 19}); 20 21const posts = data.value ? data.value[0]?.children : []; 22 23defineOgImageComponent("Page", { 24 description: `This is ${config.title}. Read all ${posts?.length || 0} posts published so far, and stay tuned for more!` 25}); 26 27const tags = 28 posts?.reduce((acc, post) => { 29 for (const tag of post.tags as string[]) { 30 if (!acc.includes(tag)) { 31 acc.push(tag); 32 } 33 } 34 return acc; 35 }, [] as string[]) ?? []; 36 37const filter = ref<string | undefined>(undefined); 38 39const filteredPosts = computed(() => { 40 if (!filter.value) return posts; 41 return posts?.filter( 42 (post) => 43 post.tags && (post.tags as string[]).includes(filter.value ?? "") 44 ); 45}); 46</script> 47 48<template> 49 <header class="mt-6"> 50 <ContentRenderer v-if="frontmatter" :value="frontmatter" class="prose prose-lg leading-7 prose-slate dark:prose-invert text-justify text-zinc-800 dark:text-zinc-200 h-frontmatter" /> 51 <p v-else> 52 Welcome to this blog template! 53 54 Change this content by editing the file <code>content/index.md</code>. 55 </p> 56 </header> 57 58 <h2 class="text-2xl font-bold my-6"> 59 Posts 60 </h2> 61 62 <section class=" overflow-x-scroll my-6 flex flex-row justify-between items-start gap-4"> 63 <p class="text-sm text-gray-500 dark:text-gray-400 w-max text-nowrap mt-1"> 64 Filter by tag: 65 </p> 66 <div class="flex flex-row items-center"> 67 <button 68 v-for="tag in tags" 69 :key="tag" 70 :class="[ 71 'flex px-3 py-1 mr-2 mb-2 w-max flex-row items-center gap-1', 72 `${tag === filter ? 'bg-stone-500 dark:bg-stone-500 text-stone-100 dark:text-stone-100' : 'bg-stone-200 dark:bg-stone-700 text-stone-600 dark:text-stone-400'}`, 73 'rounded-full text-sm font-medium lowercase text-nowrap' 74 ]" 75 @click="filter = filter === tag ? undefined : tag" 76 > 77 <Icon 78 v-if="tag === filter" 79 name="ri:close-line" 80 size="1rem" 81 mode="svg" 82 class="-ms-1" 83 /> 84 {{ tag }} 85 </button> 86 </div> 87 </section> 88 89 <PostPreviewAccent 90 v-if="filteredPosts" 91 :post="filteredPosts[0]" 92 /> 93 94 <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4 mb-8"> 95 <PostPreview 96 v-for="post in filteredPosts?.slice(1)" 97 :key="post.path" 98 :post="post" 99 /> 100 </div> 101</template>