Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ onKeyStroke(',', e => {
e.preventDefault()
router.push('/settings')
})
onKeyStroke('.', e => {
const target = e.target as HTMLElement
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) {
return
}

e.preventDefault()
router.push('/blog')
})
</script>

<template>
Expand Down Expand Up @@ -121,6 +130,21 @@ onKeyStroke(',', e => {
<li v-if="isConnected && npmUser" class="flex items-center">
<HeaderOrgsDropdown :username="npmUser" />
</li>
<li class="flex items-center">
<NuxtLink
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kai-ros I'm wondering if we should move the blog link to the footer. What do you think?

to="/blog"
class="link-subtle font-mono text-sm inline-flex items-center gap-2"
aria-keyshortcuts="."
>
{{ $t('nav.blog') }}
<kbd
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
aria-hidden="true"
>
.
</kbd>
</NuxtLink>
</li>
</ul>
</div>

Expand Down
27 changes: 27 additions & 0 deletions app/components/BlogPost.server.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
const props = defineProps<{ title: string; htmlContent: string; date: string }>()
</script>
<template>
<main class="container">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2">
{{ title }}
</h1>
<!-- <time class="text-sm text-gray-500 dark:text-gray-400"> -->

<DateTime
:datetime="date"
year="numeric"
month="short"
day="numeric"
class="text-xs text-fg-subtle"
/>

<div class="" v-html="htmlContent" />
</main>
</template>

<style>
.container {
background: var(--bg-blue);
}
</style>
41 changes: 41 additions & 0 deletions app/pages/blog/[slug].vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useAsyncData(route.path, () => {
return queryCollection('blog').path(route.path).first()
})

definePageMeta({
name: `blog-post`,
// alias: ['/:path(.*)*'],
})

useSeoMeta({
title: () => post.value?.title || 'Blog', // TODO: How does i18n deal with dynamic values? $t('blog.post.title'),
description: () => (post.value?.description ? `Blog Article ${post.value?.description}` : ''),
})
</script>

<template>
<main class="container py-8 sm:py-12 w-full">
<!-- Header -->
<header class="mb-8 pb-8 border-b border-border">
<div class="">I AM A WEAK HEADER</div>
</header>

<article v-if="post">
<ContentRenderer v-if="post" :value="post" />
</article>

<article v-else>
<h1>Post Not Found</h1>
<p>We couldn't find a post at /blog/{{ route.path }}</p>
</article>
</main>
</template>

<!-- TODO: styles -->
<style>
h1 {
@apply text-4xl font-bold text-gray-900 dark:text-white mb-2;
}
</style>
40 changes: 40 additions & 0 deletions app/pages/blog/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts">
const { data: posts } = await useAsyncData('blog-posts', () =>
queryCollection('blog').where('draft', '<>', true).order('date', 'DESC').all(),
)
definePageMeta({
name: 'blog',
// alias: ['/:path(.*)*'],
})
useSeoMeta({
title: () => $t('blog.title'),
description: () => $t('blog.description'),
})
</script>

<template>
<main class="container py-8 sm:py-12 w-full">
<header class="mb-8 pb-8 border-b border-border">
<div class="">I AM A MIGHTY HEADER</div>
</header>

<article class="flex flex-col gap-6">
<div v-for="post in posts" :key="post.slug" class="p-4 border border-border rounded-lg">
<h2 class="text-xl font-semibold">
<NuxtLink :to="`/blog/${post.slug}`" class="text-primary hover:underline">
{{ post.title }}
</NuxtLink>
</h2>
<p class="text-muted-foreground">{{ post.excerpt }}</p>
</div>
</article>
</main>
</template>

<style>
.container {
background: var(--bg-red);
}
</style>
12 changes: 12 additions & 0 deletions content.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { BlogPostSchema } from './shared/schemas/blog'

export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: BlogPostSchema,
}),
},
})
12 changes: 12 additions & 0 deletions content/blog/first-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: 'Hello World'
date: '2026-01-28'
slug: 'first-post'
description: 'My first post on the blog'
excerpt: 'My first post'
draft: false
---

# My First Page

Here is some content.
12 changes: 12 additions & 0 deletions content/blog/server-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: 'Server Components'
date: '2026-01-28'
slug: 'server-components'
description: 'My first post on the blog'
excerpt: 'Zero JS'
draft: false
---

# Server components

Here is some server component razzle dazzle. Hello there!
5 changes: 5 additions & 0 deletions i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,15 @@
"nav": {
"main_navigation": "Main navigation",
"popular_packages": "Popular packages",
"blog": "blog",
"search": "search",
"settings": "settings",
"back": "back"
},
"blog": {
"title": "Blog",
"description": "Insights and updates from the npmx community"
},
"settings": {
"title": "settings",
"tagline": "customize your npmx experience",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@
},
"nav": {
"popular_packages": "Paquets populaires",
"blog": "blog",
"search": "recherche",
"settings": "paramètres",
"back": "Retour"
},
"blog": {
"title": "Blog",
"description": "Perspectives et actualités de la communauté npmx"
},
"settings": {
"relative_dates": "Dates relatives",
"include_types": "Inclure {'@'}types à la commande d'installation",
Expand Down
5 changes: 5 additions & 0 deletions i18n/locales/it-IT.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,15 @@
},
"nav": {
"popular_packages": "Pacchetti popolari",
"blog": "blog",
"search": "cerca",
"settings": "impostazioni",
"back": "Indietro"
},
"blog": {
"title": "Blog",
"description": "Approfondimenti e aggiornamenti dalla comunità npmx"
},
"settings": {
"relative_dates": "Date relative",
"include_types": "Includi {'@'}types durante l'installazione",
Expand Down
31 changes: 31 additions & 0 deletions modules/standard-site-sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineNuxtModule, useNuxt } from 'nuxt/kit'
import * as site from '../shared/types/lexicons/site'

const PUBLICATION_SITE = 'https://npmx.dev'

export default defineNuxtModule({
meta: {
name: 'standard-site-sync',
},
setup() {
const nuxt = useNuxt()
if (nuxt.options._prepare) {
return
}
nuxt.hook('content:file:afterParse', ctx => {
const { content } = ctx

const document = site.standard.document.$build({
site: PUBLICATION_SITE,
path: content.path as string,
title: content.title as string,
description: (content.excerpt || content.description) as string | undefined,
tags: content.tags as string[] | undefined,
publishedAt: new Date(content.date as string).toISOString(),
})

// TODO: Mock PDS push
console.log('[standard-site-sync] Would push:', JSON.stringify(document, null, 2))
})
},
})
2 changes: 2 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default defineNuxtConfig({
'@vueuse/nuxt',
'@nuxtjs/i18n',
'@nuxtjs/color-mode',
'@nuxt/content',
],

colorMode: {
Expand Down Expand Up @@ -79,6 +80,7 @@ export default defineNuxtConfig({
'/**': { isr: 60 },
'/package/**': { isr: 60 },
'/search': { isr: false, cache: false },
'/blog/**': { isr: true, prerender: true },
'/_v/script.js': { proxy: 'https://npmx.dev/_vercel/insights/script.js' },
'/_v/view': { proxy: 'https://npmx.dev/_vercel/insights/view' },
'/_v/event': { proxy: 'https://npmx.dev/_vercel/insights/event' },
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@intlify/shared": "^11.2.8",
"@lunariajs/core": "https://pkg.pr.new/lunariajs/lunaria/@lunariajs/core@f07e1a3",
"@nuxt/a11y": "1.0.0-alpha.1",
"@nuxt/content": "3.11.0",
"@nuxt/fonts": "^0.13.0",
"@nuxt/scripts": "^0.13.2",
"@nuxtjs/color-mode": "^4.0.0",
Expand Down Expand Up @@ -75,6 +76,7 @@
"@types/validate-npm-package-name": "4.0.2",
"@unocss/nuxt": "66.6.0",
"@unocss/preset-wind4": "66.6.0",
"@valibot/to-json-schema": "^1.5.0",
"@vite-pwa/assets-generator": "1.0.2",
"@vite-pwa/nuxt": "1.1.0",
"@vitest/browser-playwright": "^4.0.18",
Expand Down
Loading
Loading