Merge pull request #83 from nurRiyad/content-v3

Update @nuxt/content to version 3.x and refactor content queries
This commit is contained in:
Al Asad Nur Riyad
2025-03-13 04:54:00 +06:00
committed by GitHub
16 changed files with 22688 additions and 15611 deletions

View File

@@ -1,6 +1,3 @@
# This workflow will do a clean installation of node dependencies, check linting and build
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js CI
on:
@@ -17,23 +14,20 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
cache: 'npm'
- name: Install dependencies
run: pnpm install
run: npm ci
- name: Check Linting
run: pnpm run lint
run: npm run lint
- name: Check Format
run: pnpm run format
run: npm run format
- name: Playgourd build check
run: pnpm run build
run: npm run build

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
const { path } = useRoute()
const articles = await queryContent(path).findOne()
const articles = await queryCollection('content').path(path).first()
const links = articles?.body?.toc?.links || []
</script>

View File

@@ -1,21 +1,42 @@
<script lang="ts" setup>
import type { BlogPost } from '~/types/blog'
// Function to parse dates in the format "1st Mar 2023"
function parseCustomDate(dateStr: string): Date {
// Remove ordinal indicators (st, nd, rd, th)
const cleanDateStr = dateStr.replace(/(\d+)(st|nd|rd|th)/, '$1')
// Parse the date
return new Date(cleanDateStr)
}
// Get Last 6 Publish Post from the content/blog directory
const { data } = await useAsyncData('recent-post', () =>
queryContent('/blogs').limit(3).sort({ _id: -1 }).find(),
queryCollection('content')
.all()
.then((data) => {
return data
.sort((a, b) => {
const aDate = parseCustomDate(a.meta.date as string)
const bDate = parseCustomDate(b.meta.date as string)
return bDate.getTime() - aDate.getTime()
})
.slice(0, 3)
}),
)
const formattedData = computed(() => {
return data.value?.map((articles) => {
const meta = articles.meta as unknown as BlogPost
return {
path: articles._path,
path: articles.path,
title: articles.title || 'no-title available',
description: articles.description || 'no-description available',
image: articles.image || '/not-found.jpg',
alt: articles.alt || 'no alter data available',
ogImage: articles.ogImage || '/not-found.jpg',
date: articles.date || 'not-date-available',
tags: articles.tags || [],
published: articles.published || false,
image: meta.image || '/not-found.jpg',
alt: meta.alt || 'no alter data available',
ogImage: meta.ogImage || '/not-found.jpg',
date: meta.date || 'not-date-available',
tags: meta.tags || [],
published: meta.published || false,
}
})
})

View File

@@ -1,21 +1,23 @@
<script lang="ts" setup>
// Get Last 6 Publish Post from the content/blog directory
import type { BlogPost } from '~/types/blog'
const { data } = await useAsyncData('trending-post', () =>
queryContent('/blogs').limit(3).sort({ _id: 1 }).find(),
queryCollection('content').limit(3).all(),
)
const formattedData = computed(() => {
return data.value?.map((articles) => {
const meta = articles.meta as unknown as BlogPost
return {
path: articles._path,
path: articles.path,
title: articles.title || 'no-title available',
description: articles.description || 'no-description available',
image: articles.image || '/not-found.jpg',
alt: articles.alt || 'no alter data available',
ogImage: articles.ogImage || '/not-found.jpg',
date: articles.date || 'not-date-available',
tags: articles.tags || [],
published: articles.published || false,
image: meta.image || '/not-found.jpg',
alt: meta.alt || 'no alter data available',
ogImage: meta.ogImage || '/not-found.jpg',
date: meta.date || 'not-date-available',
tags: meta.tags || [],
published: meta.published || false,
}
})
})

23
content.config.ts Normal file
View File

@@ -0,0 +1,23 @@
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { asRobotsCollection } from '@nuxtjs/robots/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'
import { asOgImageCollection } from 'nuxt-og-image/content'
export default defineContentConfig({
collections: {
content: defineCollection({
...asRobotsCollection({
type: 'page',
source: '**/*.md',
}),
...asSitemapCollection({
type: 'page',
source: '**/*.md',
}),
...asOgImageCollection({
type: 'page',
source: '**/*.md',
}),
}),
},
})

View File

@@ -5,7 +5,7 @@ export const navbarData = {
export const footerData = {
author: 'Al Asad Nur Riyad',
aboutAuthor:
'Hi! I am Riyad, a Tech enthusiast, problem solver and software engineer. Currently working at Appscode Inc.',
'Hi! I am Riyad, a Tech enthusiast, problem solver and software engineer. Currently working at FieldNation LLC.',
authorInterest:
"I have a fair amount of knowledge of Javascript, Typescript, VueJs, and Nuxt. If you have an interesting idea, either open source or paid let's connect.",
aboutTheSite:
@@ -33,13 +33,13 @@ export const aboutPage = {
title: 'Al Asad Nur Riyad',
description: 'Software Engineer, Problem Solver, Web Enthusiast.',
aboutMe:
"Hello, fellow human! I'm a software wizard who spends most of his day crafting code spells at @AppsCode in the Bytebuilders team. When I'm not crafting code, you can find me summoning solutions to problems on online judges. Just don't ask me to cast any love spells, my magic only works on machines!",
"Hello, fellow human! I'm a software wizard who spends most of his day crafting code spells at @FieldNation in the Workplace Operation team. When I'm not crafting code, you can find me summoning solutions to problems on online judges. Just don't ask me to cast any love spells, my magic only works on machines!",
}
export const seoData = {
title: `Riyad's Blog | Riyads Blog`,
ogTitle: `Let's learn Javascript, Typescript, Vue, Nuxt, & Problem Solving - Riyads Blog | Riyad's Blog`,
description: `Hi I am Riyad. A Software Engineer at AppsCode, with over 2.5+ years experience in software development. - Riyads Blog | Riyad's Blog`,
description: `Hi I am Riyad. A Software Engineer at FieldNation, with over 3.5+ years experience in software development. - Riyads Blog | Riyad's Blog`,
twitterDescription: `Riyad's Blog, where I play around with Nuxt, Vue, and more and showcase my blog, resources, etc - Riyads Blog | Riyad's Blog`,
image:
'https://res.cloudinary.com/dmecmyphj/image/upload/v1673548905/nuxt-blog/cover_ntgs6u.webp',

View File

@@ -10,10 +10,10 @@ export default defineNuxtConfig({
'@nuxt/fonts',
'@nuxt/eslint',
'@vueuse/nuxt',
'@nuxt/content',
'nuxt-og-image',
'@nuxtjs/robots',
'@nuxtjs/sitemap',
'nuxt-og-image',
'@nuxt/content',
'@nuxtjs/color-mode',
'@nuxtjs/tailwindcss',
'@formkit/auto-animate',
@@ -32,15 +32,12 @@ export default defineNuxtConfig({
},
sitemap: {
strictNuxtContentPaths: true,
sources: [seoData.mySite],
},
site: {
url: seoData.mySite,
identity: {
type: 'Person',
},
twitter: seoData.twitterHandle,
name: 'Al Asad Nur Riyad',
},
typescript: {
@@ -61,8 +58,12 @@ export default defineNuxtConfig({
},
content: {
build: {
markdown: {
highlight: {
theme: 'dracula',
},
},
},
},
})

22549
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,13 +16,13 @@
},
"devDependencies": {
"@formkit/auto-animate": "^0.8.2",
"@nuxt/content": "^2.13.4",
"@nuxt/content": "^3.3.0",
"@nuxt/eslint": "^0.6.1",
"@nuxt/fonts": "^0.10.2",
"@nuxt/image": "^1.8.0",
"@nuxtjs/color-mode": "^3.3.2",
"@nuxtjs/robots": "^4.1.7",
"@nuxtjs/sitemap": "^5.3.2",
"@nuxtjs/robots": "^5.2.6",
"@nuxtjs/sitemap": "^7.2.7",
"@nuxtjs/tailwindcss": "^6.11.4",
"@stefanobartoletti/nuxt-social-share": "^0.6.1",
"@tailwindcss/forms": "^0.5.7",
@@ -34,10 +34,9 @@
"fuse.js": "^7.0.0",
"nuxt": "^3.16.0",
"nuxt-icon": "^0.6.8",
"nuxt-og-image": "^3.1.1",
"nuxt-og-image": "^5.0.2",
"prettier": "^3.5.3",
"typescript": "^5.8.2",
"unenv": "^1.10.0"
},
"packageManager": "pnpm@9.1.1+sha256.9551e803dcb7a1839fdf5416153a844060c7bce013218ce823410532504ac10b"
}
}

View File

@@ -5,21 +5,22 @@ import { navbarData, seoData } from '~/data'
const { path } = useRoute()
const { data: articles, error } = await useAsyncData(`blog-post-${path}`, () =>
queryContent(path).findOne(),
queryCollection('content').path(path).first(),
)
if (error.value) navigateTo('/404')
const data = computed<BlogPost>(() => {
const meta = articles?.value?.meta as unknown as BlogPost
return {
title: articles.value?.title || 'no-title available',
description: articles.value?.description || 'no-description available',
image: articles.value?.image || '/not-found.jpg',
alt: articles.value?.alt || 'no alter data available',
ogImage: articles.value?.ogImage || '/not-found.jpg',
date: articles.value?.date || 'not-date-available',
tags: articles.value?.tags || [],
published: articles.value?.published || false,
image: meta?.image || '/not-found.jpg',
alt: meta?.alt || 'no alter data available',
ogImage: (articles?.value?.ogImage as unknown as string) || '/not-found.jpg',
date: meta?.date || 'not-date-available',
tags: meta?.tags || [],
published: meta?.published || false,
}
})
@@ -33,7 +34,7 @@ useHead({
},
// Test on: https://developers.facebook.com/tools/debug/ or https://socialsharepreview.com/
{ property: 'og:site_name', content: navbarData.homeTitle },
{ hid: 'og:type', property: 'og:type', content: 'website' },
{ property: 'og:type', content: 'website' },
{
property: 'og:url',
content: `${seoData.mySite}/${path}`,
@@ -78,11 +79,13 @@ useHead({
],
})
console.log(articles.value)
// Generate OG Image
defineOgImageComponent('Test', {
headline: 'Greetings 👋',
title: data.value.title || '',
description: data.value.description || '',
headline: 'Riyads Blog 👋',
title: articles.value?.seo.title || '',
description: articles.value?.seo.description || '',
link: data.value.ogImage,
})
</script>

View File

@@ -1,7 +1,8 @@
<script lang="ts" setup>
import Fuse from 'fuse.js'
import type { BlogPost } from '~/types/blog'
const { data } = await useAsyncData('home', () => queryContent('/blogs').sort({ _id: -1 }).find())
const { data } = await useAsyncData('all-blog-post', () => queryCollection('content').all())
const elementPerPage = ref(5)
const pageNumber = ref(1)
@@ -10,16 +11,17 @@ const searchTest = ref('')
const formattedData = computed(() => {
return (
data.value?.map((articles) => {
const meta = articles.meta as unknown as BlogPost
return {
path: articles._path,
path: articles.path,
title: articles.title || 'no-title available',
description: articles.description || 'no-description available',
image: articles.image || '/not-found.jpg',
alt: articles.alt || 'no alter data available',
ogImage: articles.ogImage || '/not-found.jpg',
date: articles.date || 'not-date-available',
tags: articles.tags || [],
published: articles.published || false,
image: meta.image || '/not-found.jpg',
alt: meta.alt || 'no alter data available',
ogImage: meta.ogImage || '/not-found.jpg',
date: meta.date || 'not-date-available',
tags: meta.tags || [],
published: meta.published || false,
}
}) || []
)

View File

@@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { BlogPost } from '@/types/blog'
const route = useRoute()
// take category from route params & make first char upper
@@ -12,23 +13,29 @@ const category = computed(() => {
})
const { data } = await useAsyncData(`category-data-${category.value}`, () =>
queryContent('/blogs')
.where({ tags: { $contains: category.value } })
.find(),
queryCollection('content')
.all()
.then((articles) =>
articles.filter((article) => {
const meta = article.meta as unknown as BlogPost
return meta.tags.includes(category.value)
}),
),
)
const formattedData = computed(() => {
return data.value?.map((articles) => {
const meta = articles.meta as unknown as BlogPost
return {
path: articles._path,
path: articles.path,
title: articles.title || 'no-title available',
description: articles.description || 'no-description available',
image: articles.image || '/blogs-img/blog.jpg',
alt: articles.alt || 'no alter data available',
ogImage: articles.ogImage || '/blogs-img/blog.jpg',
date: articles.date || 'not-date-available',
tags: articles.tags || [],
published: articles.published || false,
image: meta.image || '/blogs-img/blog.jpg',
alt: meta.alt || 'no alter data available',
ogImage: meta.ogImage || '/blogs-img/blog.jpg',
date: meta.date || 'not-date-available',
tags: meta.tags || [],
published: meta.published || false,
}
})
})

View File

@@ -1,14 +1,14 @@
<script lang="ts" setup>
import { makeFirstCharUpper } from '@/utils/helper'
const { data } = await useAsyncData('all-blog-post-for-category', () =>
queryContent('/blogs').sort({ _id: -1 }).find(),
const { data } = await useAsyncData('all-blog-post-by-category', () =>
queryCollection('content').all(),
)
const allTags = new Map()
data.value?.forEach((blog) => {
const tags: Array<string> = blog.tags || []
const tags: Array<string> = (blog.meta.tags as string[]) || []
tags.forEach((tag) => {
if (allTags.has(tag)) {
const cnt = allTags.get(tag)

15527
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,10 @@
import { Feed } from 'feed'
import { serverQueryContent } from '#content/server'
const basePath = 'https://nurriyad.com'
export default defineEventHandler(async (event) => {
setHeader(event, 'content-type', 'text/xml')
const docs = await serverQueryContent(event).sort({ date: -1 }).find()
const docs = await queryCollection(event, 'content').all()
const feed = new Feed({
title: "Riyad's personal blog site",
description: "Riyad's personal blog site",
@@ -23,13 +22,14 @@ export default defineEventHandler(async (event) => {
// Add the feed items
docs.forEach((doc) => {
// console.log(doc)
feed.addItem({
title: doc.title || '',
id: basePath + doc._path,
link: basePath + doc._path,
id: basePath + doc.path,
link: basePath + doc.path,
description: doc.description,
content: doc.description,
date: new Date(doc.date),
date: new Date(doc.meta?.date as string),
})
})

3
server/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}