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>