From c7e5eb0713557f5eacbf0f769fbeeca9e0059515 Mon Sep 17 00:00:00 2001 From: mahdiarghyani Date: Sun, 9 Nov 2025 13:56:03 +0330 Subject: [PATCH 01/13] add blog to project --- .gitignore | 1 + .kiro/specs/nuxt-content-blog/design.md | 1321 +++++++++++++++++ .kiro/specs/nuxt-content-blog/requirements.md | 241 +++ .kiro/specs/nuxt-content-blog/tasks.md | 315 ++++ .vscode/settings.json | 5 +- app/components/blog/BlogCard.vue | 42 + app/components/blog/BlogEmpty.vue | 17 + app/components/blog/BlogNavigation.vue | 73 + app/components/blog/BlogPost.vue | 53 + app/components/blog/BlogSearch.vue | 35 + app/components/blog/BlogTableOfContents.vue | 151 ++ app/components/blog/BlogTagFilter.vue | 48 + app/composables/useBlog.ts | 88 ++ app/pages/blog/[...slug].vue | 179 ++- app/pages/blog/index.vue | 66 +- app/types/blog.ts | 44 + content.config.ts | 20 + content/en/blog/draft-post.md | 14 + .../blog/getting-started-with-nuxt-content.md | 108 ++ content/en/blog/typescript-best-practices.md | 133 ++ content/fa/blog/nuxt-content-introduction.md | 108 ++ content/fa/blog/vue-composition-api.md | 179 +++ i18n/locales/en.json | 17 +- i18n/locales/fa.json | 17 +- nuxt.config.ts | 34 +- public/img/blog/default-cover.svg | 12 + 26 files changed, 3310 insertions(+), 11 deletions(-) create mode 100644 .kiro/specs/nuxt-content-blog/design.md create mode 100644 .kiro/specs/nuxt-content-blog/requirements.md create mode 100644 .kiro/specs/nuxt-content-blog/tasks.md create mode 100644 app/components/blog/BlogCard.vue create mode 100644 app/components/blog/BlogEmpty.vue create mode 100644 app/components/blog/BlogNavigation.vue create mode 100644 app/components/blog/BlogPost.vue create mode 100644 app/components/blog/BlogSearch.vue create mode 100644 app/components/blog/BlogTableOfContents.vue create mode 100644 app/components/blog/BlogTagFilter.vue create mode 100644 app/composables/useBlog.ts create mode 100644 app/types/blog.ts create mode 100644 content.config.ts create mode 100644 content/en/blog/draft-post.md create mode 100644 content/en/blog/getting-started-with-nuxt-content.md create mode 100644 content/en/blog/typescript-best-practices.md create mode 100644 content/fa/blog/nuxt-content-introduction.md create mode 100644 content/fa/blog/vue-composition-api.md create mode 100644 public/img/blog/default-cover.svg diff --git a/.gitignore b/.gitignore index 66bcc1f..3286ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .nuxt .nitro .cache +.data dist # Node dependencies diff --git a/.kiro/specs/nuxt-content-blog/design.md b/.kiro/specs/nuxt-content-blog/design.md new file mode 100644 index 0000000..6ca2264 --- /dev/null +++ b/.kiro/specs/nuxt-content-blog/design.md @@ -0,0 +1,1321 @@ +# Blog System Design Document + +## Overview + +This document outlines the technical design for implementing a fully-featured blog system in the Nuxt 4 portfolio application using Nuxt Content v3. The design integrates seamlessly with the existing architecture, leveraging Nuxt UI components, i18n for bilingual support, and modern performance optimization techniques. + +### Design Goals + +1. **Content-First Architecture**: Use file-based markdown content management for simplicity and version control +2. **Seamless Integration**: Maintain consistency with existing portfolio design system and navigation +3. **Bilingual Support**: Full English and Persian (RTL) support with locale-specific content +4. **Performance**: Optimize for fast page loads, SEO, and Core Web Vitals +5. **Developer Experience**: Hot-reload, TypeScript safety, and intuitive content authoring +6. **Extensibility**: Support for MDC components, custom frontmatter, and future enhancements + +### Technology Stack + +- **Content Management**: @nuxt/content v3.x +- **UI Components**: Nuxt UI v4 (existing) +- **Styling**: Tailwind CSS v4 (existing) +- **Internationalization**: @nuxtjs/i18n (existing) +- **Image Optimization**: @nuxt/image (existing) +- **Syntax Highlighting**: Shiki (built-in with Nuxt Content) +- **Type Safety**: TypeScript with custom interfaces + +## Architecture + +### High-Level Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Nuxt 4 Application │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ │ +│ │ Pages │ │ Components │ │ +│ │ │ │ │ │ +│ │ /blog/ │─────▶│ BlogList │ │ +│ │ index.vue │ │ BlogCard │ │ +│ │ │ │ BlogSearch │ │ +│ │ /blog/ │ │ TagFilter │ │ +│ │ [...slug] │─────▶│ BlogPost │ │ +│ │ .vue │ │ TableOfContents │ +│ └──────────────┘ │ BlogNav │ │ +│ │ └──────────────┘ │ +│ │ │ │ +│ ▼ ▼ │ +│ ┌─────────────────────────────────────┐ │ +│ │ Nuxt Content Module │ │ +│ │ - queryContent() API │ │ +│ │ - ContentDoc component │ │ +│ │ - ContentRenderer component │ │ +│ │ - useContentHead() composable │ │ +│ └─────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────┐ │ +│ │ Content Directory │ │ +│ │ content/ │ │ +│ │ ├── en/blog/*.md │ │ +│ │ └── fa/blog/*.md │ │ +│ └─────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + + +### Directory Structure + +``` +project-root/ +├── content/ # Content directory (new) +│ ├── en/ +│ │ └── blog/ +│ │ ├── my-first-post.md +│ │ ├── nuxt-tips.md +│ │ └── tutorials/ +│ │ └── getting-started.md +│ └── fa/ +│ └── blog/ +│ ├── first-post-fa.md +│ └── nuxt-tips-fa.md +│ +├── app/ +│ ├── pages/ +│ │ └── blog/ +│ │ ├── index.vue # Blog listing page (existing, to be updated) +│ │ └── [...slug].vue # Blog detail page (existing, to be updated) +│ │ +│ ├── components/ +│ │ └── blog/ # Blog components (new) +│ │ ├── BlogCard.vue +│ │ ├── BlogList.vue +│ │ ├── BlogSearch.vue +│ │ ├── BlogTagFilter.vue +│ │ ├── BlogPost.vue +│ │ ├── BlogTableOfContents.vue +│ │ ├── BlogNavigation.vue +│ │ └── BlogEmpty.vue +│ │ +│ ├── composables/ +│ │ └── useBlog.ts # Blog utilities composable (new) +│ │ +│ ├── types/ +│ │ └── blog.ts # Blog TypeScript types (new) +│ │ +│ └── public/ +│ └── img/ +│ └── blog/ # Blog images (new) +│ └── default-cover.jpg +│ +├── server/ +│ └── routes/ +│ └── blog/ +│ └── rss.xml.ts # RSS feed generator (new) +│ +└── nuxt.config.ts # Updated with @nuxt/content +``` + +### Content Flow + +1. **Content Creation**: Developer writes markdown files in `content/{locale}/blog/` +2. **Content Parsing**: Nuxt Content parses markdown and frontmatter on server start +3. **Content Query**: Pages use `queryContent()` to fetch filtered/sorted content +4. **Content Rendering**: `ContentRenderer` transforms markdown to HTML with Vue components +5. **Content Display**: Nuxt UI components style the rendered content + +## Components and Interfaces + +### Page Components + +#### 1. Blog Listing Page (`app/pages/blog/index.vue`) + +**Purpose**: Display all published blog posts with search and filtering capabilities + +**Key Features**: +- Fetch posts using `queryContent()` +- Search functionality with debounce +- Tag filtering +- Responsive grid layout +- Empty state handling +- Locale-aware content fetching + +**Component Structure**: +```vue + +``` + +**Data Fetching Strategy**: +```typescript +const { locale } = useI18n() +const route = useRoute() + +// Fetch posts for current locale +const { data: posts } = await useAsyncData('blog-posts', () => + queryContent(`${locale.value}/blog`) + .where({ draft: { $ne: true } }) + .sort({ date: -1 }) + .only(['title', 'description', 'date', 'tags', '_path', 'image']) + .find() +) +``` + + +#### 2. Blog Detail Page (`app/pages/blog/[...slug].vue`) + +**Purpose**: Render individual blog post with full content and metadata + +**Key Features**: +- Fetch single post by slug +- Render markdown content with syntax highlighting +- Display metadata (title, date, tags, reading time) +- Table of contents for long posts +- Previous/next post navigation +- SEO meta tags +- Breadcrumb navigation + +**Component Structure**: +```vue + +``` + +**Data Fetching Strategy**: +```typescript +const { locale } = useI18n() +const route = useRoute() +const slug = route.params.slug as string[] + +// Fetch current post +const { data: post } = await useAsyncData(`blog-post-${slug.join('/')}`, () => + queryContent(`${locale.value}/blog`) + .where({ _path: `/${locale.value}/blog/${slug.join('/')}` }) + .findOne() +) + +if (!post.value) { + throw createError({ statusCode: 404, message: 'Post not found' }) +} + +// Fetch adjacent posts for navigation +const { data: adjacentPosts } = await useAsyncData('adjacent-posts', () => + queryContent(`${locale.value}/blog`) + .where({ draft: { $ne: true } }) + .sort({ date: -1 }) + .only(['title', '_path', 'date']) + .find() +) +``` + +### UI Components + +#### 1. BlogCard Component + +**Purpose**: Display blog post preview in listing page + +**Props**: +```typescript +interface BlogCardProps { + post: BlogPost +} +``` + +**Features**: +- Cover image with lazy loading +- Title and description +- Formatted date +- Reading time estimate +- Tags as badges +- Hover effects +- Click to navigate + +**Implementation Notes**: +- Use `UCard` from Nuxt UI as base +- Use `NuxtImg` for optimized images +- Use `UBadge` for tags +- Use `localePath()` for navigation + +#### 2. BlogSearch Component + +**Purpose**: Search input for filtering posts + +**Props**: +```typescript +interface BlogSearchProps { + modelValue: string +} + +interface BlogSearchEmits { + 'update:modelValue': [value: string] +} +``` + +**Features**: +- Debounced input (300ms) +- Clear button +- Search icon +- Placeholder text (i18n) + +**Implementation Notes**: +- Use `UInput` with icon slots +- Use `useDebounceFn` from VueUse +- Emit updates to parent + +#### 3. BlogTagFilter Component + +**Purpose**: Display and filter by tags + +**Props**: +```typescript +interface BlogTagFilterProps { + tags: string[] + modelValue: string | null +} +``` + +**Features**: +- Display all unique tags +- Highlight active tag +- Clear filter option +- Responsive layout + +**Implementation Notes**: +- Use `UButton` or `UBadge` for tags +- Use query parameters for state persistence +- Horizontal scroll on mobile + + +#### 4. BlogTableOfContents Component + +**Purpose**: Display navigable table of contents for long posts + +**Props**: +```typescript +interface TocLink { + id: string + text: string + depth: number + children?: TocLink[] +} + +interface BlogTableOfContentsProps { + toc: { + links: TocLink[] + } +} +``` + +**Features**: +- Nested heading structure +- Active section highlighting +- Smooth scroll to sections +- Sticky positioning on desktop +- Collapsible on mobile + +**Implementation Notes**: +- Use `IntersectionObserver` for active tracking +- Use `scrollIntoView({ behavior: 'smooth' })` for navigation +- Use `UAccordion` for mobile collapsible version + +#### 5. BlogNavigation Component + +**Purpose**: Previous/next post navigation + +**Props**: +```typescript +interface BlogNavigationProps { + prev: BlogPost | null + next: BlogPost | null +} +``` + +**Features**: +- Previous and next post links +- Post titles +- Directional arrows +- Keyboard navigation support + +**Implementation Notes**: +- Use `UButton` with icon slots +- Use `@keydown` for arrow key navigation +- Use flexbox for layout + +#### 6. BlogPost Component + +**Purpose**: Display post metadata header + +**Props**: +```typescript +interface BlogPostProps { + post: BlogPost +} +``` + +**Features**: +- Post title (h1) +- Formatted publish date +- Reading time estimate +- Author info (if available) +- Tags +- Cover image + +**Implementation Notes**: +- Use semantic HTML (`
`, `
`, `