Skip to content

Commit 651fe07

Browse files
Kai-rosjonathanyeong
authored andcommitted
feat: barebones implementation of Blog Posts UI (WIP)
1 parent 3ff7798 commit 651fe07

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
@@ -26,6 +26,15 @@ onKeyStroke(',', e => {
2626
e.preventDefault()
2727
router.push('/settings')
2828
})
29+
onKeyStroke('.', e => {
30+
const target = e.target as HTMLElement
31+
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
32+
return
33+
}
34+
35+
e.preventDefault()
36+
router.push('/blog')
37+
})
2938
</script>
3039

3140
<template>
@@ -69,6 +78,21 @@ onKeyStroke(',', e => {
6978
<li v-if="isConnected && npmUser" class="flex items-center">
7079
<HeaderOrgsDropdown :username="npmUser" />
7180
</li>
81+
<li class="flex items-center">
82+
<NuxtLink
83+
to="/blog"
84+
class="link-subtle font-mono text-sm inline-flex items-center gap-2"
85+
aria-keyshortcuts="."
86+
>
87+
{{ $t('nav.blog') }}
88+
<kbd
89+
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
90+
aria-hidden="true"
91+
>
92+
.
93+
</kbd>
94+
</NuxtLink>
95+
</li>
7296
</ul>
7397
</div>
7498

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
@@ -41,10 +41,15 @@
4141
"nav": {
4242
"main_navigation": "Main",
4343
"popular_packages": "Popular packages",
44+
"blog": "blog",
4445
"search": "search",
4546
"settings": "settings",
4647
"back": "back"
4748
},
49+
"blog": {
50+
"title": "Blog",
51+
"description": "Insights and updates from the npmx community"
52+
},
4853
"settings": {
4954
"title": "settings",
5055
"tagline": "customize your npmx experience",

i18n/locales/fr-FR.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@
4141
"nav": {
4242
"main_navigation": "Barre de navigation",
4343
"popular_packages": "Paquets populaires",
44+
"blog": "blog",
4445
"search": "recherche",
4546
"settings": "paramètres",
4647
"back": "Retour"
4748
},
49+
"blog": {
50+
"title": "Blog",
51+
"description": "Perspectives et actualités de la communauté npmx"
52+
},
4853
"settings": {
4954
"title": "paramètres",
5055
"tagline": "personnalisez votre expérience npmx",

i18n/locales/it-IT.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@
4141
"nav": {
4242
"main_navigation": "Principale",
4343
"popular_packages": "Pacchetti popolari",
44+
"blog": "blog",
4445
"search": "cerca",
4546
"settings": "impostazioni",
4647
"back": "Indietro"
4748
},
49+
"blog": {
50+
"title": "Blog",
51+
"description": "Approfondimenti e aggiornamenti dalla comunità npmx"
52+
},
4853
"settings": {
4954
"title": "impostazioni",
5055
"tagline": "personalizza la tua esperienza npmx",

nuxt.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export default defineNuxtConfig({
8888
'/about': { prerender: true },
8989
'/settings': { prerender: true },
9090
// proxy for insights
91+
'/blog/**': { isr: true, prerender: true },
9192
'/_v/script.js': { proxy: 'https://npmx.dev/_vercel/insights/script.js' },
9293
'/_v/view': { proxy: 'https://npmx.dev/_vercel/insights/view' },
9394
'/_v/event': { proxy: 'https://npmx.dev/_vercel/insights/event' },

0 commit comments

Comments
 (0)