Skip to content

Commit 5bee41f

Browse files
Kai-rosjonathanyeong
authored andcommitted
feat: barebones implementation of Blog Posts UI (WIP)
1 parent bed96e7 commit 5bee41f

8 files changed

Lines changed: 174 additions & 0 deletions

File tree

app/components/AppHeader.vue

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ onKeyStroke(',', e => {
4848
e.preventDefault()
4949
router.push('/settings')
5050
})
51+
onKeyStroke('.', e => {
52+
const target = e.target as HTMLElement
53+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
54+
return
55+
}
56+
57+
e.preventDefault()
58+
router.push('/blog')
59+
})
5160
</script>
5261

5362
<template>
@@ -121,6 +130,21 @@ onKeyStroke(',', e => {
121130
<li v-if="isConnected && npmUser" class="flex items-center">
122131
<HeaderOrgsDropdown :username="npmUser" />
123132
</li>
133+
<li class="flex items-center">
134+
<NuxtLink
135+
to="/blog"
136+
class="link-subtle font-mono text-sm inline-flex items-center gap-2"
137+
aria-keyshortcuts="."
138+
>
139+
{{ $t('nav.blog') }}
140+
<kbd
141+
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
142+
aria-hidden="true"
143+
>
144+
.
145+
</kbd>
146+
</NuxtLink>
147+
</li>
124148
</ul>
125149
</div>
126150

app/components/BlogPost.server.vue

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<script setup lang="ts">
2+
const props = defineProps<{ title: string; htmlContent: string; date: string }>()
3+
</script>
4+
<template>
5+
<main class="container">
6+
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2">
7+
{{ title }}
8+
</h1>
9+
<!-- <time class="text-sm text-gray-500 dark:text-gray-400"> -->
10+
11+
<DateTime
12+
:datetime="date"
13+
year="numeric"
14+
month="short"
15+
day="numeric"
16+
class="text-xs text-fg-subtle"
17+
/>
18+
19+
<div class="" v-html="htmlContent" />
20+
</main>
21+
</template>
22+
23+
<style>
24+
.container {
25+
background: var(--bg-blue);
26+
}
27+
</style>

app/pages/blog/[...path].vue

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script setup lang="ts">
2+
const route = useRoute('blog-post')
3+
const router = useRouter()
4+
5+
interface Post {
6+
title: string
7+
date: string
8+
body: string
9+
}
10+
11+
const fakePosts: Record<string, Post> = {
12+
'hello-world': {
13+
title: 'Hello World',
14+
date: 'Jan 28, 2026',
15+
body: '<p>This is the first post.</p>',
16+
},
17+
'server-components': {
18+
title: 'Server Components',
19+
date: 'Jan 29, 2026',
20+
body: '<p>Zero JS is great.</p>',
21+
},
22+
}
23+
24+
const slug = computed<string>(() => {
25+
const pathParam = route.params.path
26+
return Array.isArray(pathParam) ? pathParam.join('/') : pathParam
27+
})
28+
29+
const post = computed(() => {
30+
const s = slug.value
31+
return s ? fakePosts[s] : null
32+
})
33+
34+
definePageMeta({
35+
name: 'blog-post',
36+
// alias: ['/:path(.*)*'],
37+
})
38+
39+
useSeoMeta({
40+
title: () => $t('blog.post.title'),
41+
42+
// description: () => `Blog Article ${post.value}@${post.value}`,
43+
})
44+
</script>
45+
46+
<template>
47+
<main class="container py-8 sm:py-12 w-full">
48+
<!-- Header -->
49+
<header class="mb-8 pb-8 border-b border-border">
50+
<div class="">I AM A WEAK HEADER</div>
51+
</header>
52+
53+
<article v-if="post">
54+
<BlogPost :title="post.title" :date="post.date" :html-content="post.body" />
55+
</article>
56+
57+
<article v-else>
58+
<h1>Post Not Found</h1>
59+
<p>We couldn't find a post at /blog/{{ slug }}</p>
60+
</article>
61+
</main>
62+
</template>

app/pages/blog/index.vue

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<script setup lang="ts">
2+
const route = useRoute('blog')
3+
const router = useRouter()
4+
5+
// 1. Define your list of posts (This would eventually come from useAsyncData)
6+
const posts = [
7+
{ slug: 'hello-world', title: 'Hello World', excerpt: 'My first post' },
8+
{ slug: 'server-components', title: 'Server Components', excerpt: 'Zero JS' },
9+
]
10+
11+
definePageMeta({
12+
name: 'blog',
13+
// alias: ['/:path(.*)*'],
14+
})
15+
16+
useSeoMeta({
17+
title: () => $t('blog.title'),
18+
description: () => $t('blog.description'),
19+
})
20+
</script>
21+
22+
<template>
23+
<main class="container py-8 sm:py-12 w-full">
24+
<header class="mb-8 pb-8 border-b border-border">
25+
<div class="">I AM A MIGHTY HEADER</div>
26+
</header>
27+
28+
<article class="flex flex-col gap-6">
29+
<div v-for="post in posts" :key="post.slug" class="p-4 border border-border rounded-lg">
30+
<h2 class="text-xl font-semibold">
31+
<NuxtLink :to="`/blog/${post.slug}`" class="text-primary hover:underline">
32+
{{ post.title }}
33+
</NuxtLink>
34+
</h2>
35+
<p class="text-muted-foreground">{{ post.excerpt }}</p>
36+
</div>
37+
</article>
38+
</main>
39+
</template>
40+
41+
<style>
42+
.container {
43+
background: var(--bg-red);
44+
}
45+
</style>

i18n/locales/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,15 @@
4242
"nav": {
4343
"main_navigation": "Main navigation",
4444
"popular_packages": "Popular packages",
45+
"blog": "blog",
4546
"search": "search",
4647
"settings": "settings",
4748
"back": "back"
4849
},
50+
"blog": {
51+
"title": "Blog",
52+
"description": "Insights and updates from the npmx community"
53+
},
4954
"settings": {
5055
"title": "settings",
5156
"tagline": "customize your npmx experience",

i18n/locales/fr-FR.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@
3939
},
4040
"nav": {
4141
"popular_packages": "Paquets populaires",
42+
"blog": "blog",
4243
"search": "recherche",
4344
"settings": "paramètres",
4445
"back": "Retour"
4546
},
47+
"blog": {
48+
"title": "Blog",
49+
"description": "Perspectives et actualités de la communauté npmx"
50+
},
4651
"settings": {
4752
"relative_dates": "Dates relatives",
4853
"include_types": "Inclure {'@'}types à la commande d'installation",

i18n/locales/it-IT.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@
3939
},
4040
"nav": {
4141
"popular_packages": "Pacchetti popolari",
42+
"blog": "blog",
4243
"search": "cerca",
4344
"settings": "impostazioni",
4445
"back": "Indietro"
4546
},
47+
"blog": {
48+
"title": "Blog",
49+
"description": "Approfondimenti e aggiornamenti dalla comunità npmx"
50+
},
4651
"settings": {
4752
"relative_dates": "Date relative",
4853
"include_types": "Includi {'@'}types durante l'installazione",

nuxt.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export default defineNuxtConfig({
7979
'/**': { isr: 60 },
8080
'/package/**': { isr: 60 },
8181
'/search': { isr: false, cache: false },
82+
'/blog/**': { isr: true, prerender: true },
8283
'/_v/script.js': { proxy: 'https://npmx.dev/_vercel/insights/script.js' },
8384
'/_v/view': { proxy: 'https://npmx.dev/_vercel/insights/view' },
8485
'/_v/event': { proxy: 'https://npmx.dev/_vercel/insights/event' },

0 commit comments

Comments
 (0)