Skip to content

Commit 087a66b

Browse files
committed
feat(theme-press): add clickable tags and tags layout page
- Make tags in PressArticle clickable, linking to /tags/?tag=xxx - Add layouts/tags.vue with tag cloud and filtered post list - Support light/dark mode for active tag styling - Add docs/pages/tags/index.md for docs site
1 parent 1079625 commit 087a66b

File tree

4 files changed

+94
-4
lines changed

4 files changed

+94
-4
lines changed

docs/pages/tags/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
title: Tags
3+
layout: tags
4+
---

packages/valaxy-theme-press/components/PressArticle.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ const updatedDate = computed(() => frontmatter.value.updated ? formatDate(frontm
5151
</span>
5252
</div>
5353
<div v-if="frontmatter.tags?.length || frontmatter.categories" class="mt-3 flex items-center justify-center gap-2 flex-wrap text-xs">
54-
<span
54+
<RouterLink
5555
v-for="tag in (Array.isArray(frontmatter.tags) ? frontmatter.tags : [])" :key="tag"
56-
class="px-2 py-0.5 rounded-full bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"
56+
class="px-2 py-0.5 rounded-full bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400 no-underline hover:bg-primary/10 hover:text-primary transition-colors"
57+
:to="{ path: '/tags/', query: { tag } }"
5758
>
5859
#{{ tag }}
59-
</span>
60+
</RouterLink>
6061
</div>
6162
</header>
6263

packages/valaxy-theme-press/components/PressArticleCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const { $tO } = useValaxyI18n()
2121
</h2>
2222
<div
2323
v-if="post.excerpt"
24-
class="press-article-card-excerpt prose dark:prose-invert max-w-none text-gray-500"
24+
class="press-article-card-excerpt prose dark:prose-invert max-w-none"
2525
>
2626
<ValaxyDynamicComponent :template-str="post.excerpt" />
2727
</div>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script lang="ts" setup>
2+
import { useFrontmatter, useSiteStore, useTags, useValaxyI18n } from 'valaxy'
3+
import { computed } from 'vue'
4+
import { useRoute, useRouter } from 'vue-router'
5+
6+
const route = useRoute()
7+
const router = useRouter()
8+
const frontmatter = useFrontmatter()
9+
const site = useSiteStore()
10+
const { $tO } = useValaxyI18n()
11+
12+
const tags = useTags()
13+
14+
const curTag = computed(() => route.query.tag as string || '')
15+
16+
const filteredPosts = computed(() => {
17+
return site.postList.filter((post) => {
18+
if (post.tags) {
19+
if (typeof post.tags === 'string')
20+
return post.tags === curTag.value
21+
else
22+
return post.tags.includes(curTag.value)
23+
}
24+
return false
25+
})
26+
})
27+
28+
const title = computed(() => $tO(frontmatter.value.title) || 'Tags')
29+
30+
const tagArr = computed(() => Array.from(tags.value).sort())
31+
32+
function getTagSize(count: number) {
33+
const counts = Array.from(tags.value).map(([_, v]) => v.count)
34+
const max = Math.max(...counts)
35+
const min = Math.min(...counts)
36+
const range = max - min || 1
37+
const percent = (count - min) / range
38+
return `${percent * 20 + 14}px`
39+
}
40+
41+
function selectTag(tag: string) {
42+
router.push({ query: { tag } })
43+
}
44+
</script>
45+
46+
<template>
47+
<Layout>
48+
<template #main-content>
49+
<div class="max-w-6xl mx-auto w-full pb-12 px-4 sm:px-6 lg:px-8">
50+
<div class="pt-6 pb-8 space-y-2 md:space-y-5">
51+
<h1 class="text-3xl leading-9 font-extrabold text-gray-900 dark:text-gray-100 tracking-tight sm:text-4xl sm:leading-10 md:text-5xl md:leading-13">
52+
{{ title }}
53+
</h1>
54+
</div>
55+
56+
<!-- Tag Cloud -->
57+
<div class="flex flex-wrap items-end gap-3 pb-8">
58+
<button
59+
v-for="([key, tag]) in tagArr"
60+
:key="key"
61+
class="inline-flex items-baseline gap-1 rounded-full px-3 py-1 transition-colors border-none cursor-pointer"
62+
:class="curTag === key
63+
? 'bg-primary/15 text-primary dark:bg-primary/20 dark:text-primary'
64+
: 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400 hover:bg-primary/10 hover:text-primary'"
65+
:style="{ fontSize: getTagSize(tag.count) }"
66+
@click="selectTag(key.toString())"
67+
>
68+
<span>#{{ key }}</span>
69+
<span class="text-xs op-70">({{ tag.count }})</span>
70+
</button>
71+
</div>
72+
73+
<!-- Filtered Posts -->
74+
<div v-if="curTag">
75+
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100 mb-6 flex items-center gap-2">
76+
<span class="i-ri-hashtag" />
77+
{{ curTag }}
78+
<span class="text-sm font-normal text-gray-500">({{ filteredPosts.length }})</span>
79+
</h2>
80+
<PressPostList :posts="filteredPosts" />
81+
</div>
82+
</div>
83+
</template>
84+
</Layout>
85+
</template>

0 commit comments

Comments
 (0)