Skip to content

Commit

Permalink
feat: Add dynamic categories page and improve header interaction
Browse files Browse the repository at this point in the history
1. Enable users to configure categories (links, titles, icons) via config.yml.
2. Move type interfaces from getConfig to global types for better type management.
3. Implement dynamic category page generation based on user settings.
4. Update post pages to make thumbnails and titles clickable, and link categories to their respective category pages (via CategoryLinks.tsx).
5. Enhance accessibility in the header and implement desktop hover interaction on the "Posts" menu to display user-defined categories, with indentation for categories on mobile.
6. Refactor services folder structure, add subfolders, and update imports accordingly.
  • Loading branch information
ZL-Asica committed Oct 21, 2024
1 parent f6ee3fa commit dcc66d5
Show file tree
Hide file tree
Showing 25 changed files with 464 additions and 204 deletions.
19 changes: 18 additions & 1 deletion config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ background: '/images/background.jpg'
# Slogan displayed on your homepage, under your avatar.
slogan: "As long as the code or the developer is able to run, it's all good."

#######################
# HEADER SETTINGS
#######################
# Post Categories: Add your post categories here.
# Use icons from link below (e.g., 'FaHouse' for 'fa6 FaHouse').
# https://react-icons.github.io/react-icons/icons/fa6/
postCategories:
- name: '前端' # Category name, same ad frontmatter category, will show in frontend.
slug: 'frontend' # (Optional) Category slug, if not set, will be same as name.
icon: 'FaHtml5' # (Optional) Font Awesome 6 icon name. If not set, no icon.
- name: '全栈'
slug: 'fullstack'
icon: 'FaCode'
- name: '教程'
slug: 'tutorial'
icon: 'FaGraduationCap'

#######################
# SOCIAL MEDIA SETTINGS
#######################
Expand All @@ -34,7 +51,7 @@ socialMedia:
bilibili: 'https://space.bilibili.com/29018759' # Bilibili profile URL.
zhihu: 'https://www.zhihu.com/people/zl-asica' # Zhihu profile URL.
email: '[email protected]' # Your email address (do NOT include the mailto: prefix).
rss: '' # Optional: RSS feed URL ().
rss: '' # RSS feed URL (can be relative url).

#######################
# PAGES (ABOUT, FRIENDS) SETTINGS
Expand Down
2 changes: 1 addition & 1 deletion src/app/about/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';

const config = getConfig();

Expand Down
4 changes: 2 additions & 2 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getPostData } from '@/services/posts';
import { getPostData } from '@/services/content/posts';
import { PostData } from '@/types';
import PostLayout from '@/components/layout/PostLayout';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';

export default async function AboutPage() {
const post: PostData = await getPostData('About', 'About');
Expand Down
43 changes: 43 additions & 0 deletions src/app/categories/[categorySlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { getAllPosts } from '@/services/content/posts';
import { getConfig } from '@/services/config/getConfig';
import { notFound } from 'next/navigation';
import PostListLayout from '@/components/layout/PostListLayout';

export async function generateStaticParams() {
const config = getConfig();
return config.postCategories.map((category) => ({
categorySlug: category.slug,
}));
}

export default async function CategoryPage({
params,
}: {
params: { categorySlug: string };
}) {
const posts = await getAllPosts();
const config = getConfig();

// Find the category based on the slug from params
const category = config.postCategories.find(
(cat) => cat.slug === params.categorySlug,
);

if (!category) {
// If the category doesn't exist, show 404
notFound();
}

// Filter posts by the category name
const filteredPosts = posts.filter((post) =>
post.frontmatter.categories?.includes(category.name),
);

return (
<div className='container mx-auto p-4'>
<h1 className='mb-6 text-center text-4xl font-bold'>{category.name}</h1>

<PostListLayout posts={filteredPosts} />
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/friends/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';

const config = getConfig();

Expand Down
4 changes: 2 additions & 2 deletions src/app/friends/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getPostData } from '@/services/posts';
import { getPostData } from '@/services/content/posts';
import { PostData } from '@/types';
import PostLayout from '@/components/layout/PostLayout';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';
import '@/styles/friendsLinks.css';

export default async function FriendsPage() {
Expand Down
7 changes: 5 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import './globals.css';
import ThemeProvider from '@/components/layout/ThemeProvider';
import Header from '@/components/common/Header';
import Footer from '@/components/common/Footer';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';
import Script from 'next/script';

const config = getConfig();
Expand Down Expand Up @@ -52,7 +52,10 @@ export default function RootLayout({
))}
<body className={`${roboto.variable} ${notoSansSC.variable} antialiased`}>
<ThemeProvider />
<Header siteTitle={config.title} />
<Header
siteTitle={config.title}
postCategories={config.postCategories}
/>
<main>{children}</main>
<Footer />

Expand Down
2 changes: 1 addition & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Image from 'next/image';
import PostsPage from './posts/page';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';

export default function Home() {
const config = getConfig();
Expand Down
2 changes: 1 addition & 1 deletion src/app/posts/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getPostData } from '@/services/posts';
import { getPostData } from '@/services/content/posts';
import { PostData } from '@/types';
import PostLayout from '@/components/layout/PostLayout';

Expand Down
2 changes: 1 addition & 1 deletion src/app/posts/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';

const config = getConfig();

Expand Down
76 changes: 3 additions & 73 deletions src/app/posts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,15 @@
import { getAllPosts } from '@/services/posts';
import Link from 'next/link';
import PostListLayout from '@/components/layout/PostListLayout';
import { getAllPosts } from '@/services/content/posts';
import { PostData } from '@/types';
import Image from 'next/image';
import { FaFolder, FaRegClock, FaEye } from 'react-icons/fa6';

export default async function PostsPage() {
const posts: PostData[] = await getAllPosts();

// TODO: Replace with actual read count
const readCount: number = 29;

posts.forEach((post) => {
const plainText = post.contentHtml
.replace(/<!--more-->/g, '[[MORE_PLACEHOLDER]]')
.replace(/<[^>]*>/g, '');

const moreIndex = plainText.indexOf('[[MORE_PLACEHOLDER]]');

post.contentHtml =
moreIndex > 0
? plainText.slice(0, moreIndex).replace('[[MORE_PLACEHOLDER]]', '')
: plainText.slice(0, 150);
});

return (
<div className='container mx-auto p-4'>
<h1 className='mb-6 text-center text-4xl font-bold'>All Posts</h1>

<div className='grid grid-cols-1 gap-6'>
{posts.map((post, index) => (
<Link
key={post.slug}
href={`/posts/${post.slug}`}
className={`flex flex-col overflow-hidden rounded-lg shadow-lg transition-shadow duration-300 hover:shadow-xl md:flex-row ${index % 2 !== 0 ? 'md:flex-row-reverse' : ''} mx-auto`}
style={{ maxWidth: '780px', maxHeight: '400px' }} // Set max width and height
>
{/* Thumbnail */}
<div className='max-h-[400px] w-full md:w-1/2'>
{post.frontmatter.thumbnail && (
<Image
src={post.frontmatter.thumbnail}
alt={post.frontmatter.title}
width={780}
height={400}
className='h-full w-full object-cover'
/>
)}
</div>

{/* Content */}
<div className='flex flex-col justify-between p-4 md:w-1/2'>
<div>
{/* Date of Publish */}
<div className='text-gray-450 mb-2 flex items-center text-sm'>
<FaRegClock className='mr-2' />
<span>{post.frontmatter.date.split(' ')[0]}</span>
</div>
{/* Title in Frontmatter */}
<h2 className='mb-2 text-2xl font-bold'>
{post.frontmatter.title}
</h2>
<p className='text-sm text-gray-300'>{post.contentHtml}</p>
</div>
<div className='mt-4'>
<div className='text-gray-450 flex items-center justify-between text-sm'>
{/* Read Count */}
<span className='flex items-center'>
<FaEye className='mr-1' />
{readCount} 热度
</span>
{/* Category */}
<span className='flex items-center'>
<FaFolder className='mr-1' />
{post.frontmatter.categories?.join(', ') || '未分类'}
</span>
</div>
</div>
</div>
</Link>
))}
</div>
<PostListLayout posts={posts} />
</div>
);
}
2 changes: 1 addition & 1 deletion src/components/common/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from 'react-icons/fa6';
import { SiZhihu } from 'react-icons/si';
import { MdEmail } from 'react-icons/md';
import { getConfig } from '@/services/getConfig';
import { getConfig } from '@/services/config/getConfig';
import Link from 'next/link';

export default function Footer() {
Expand Down
Loading

0 comments on commit dcc66d5

Please sign in to comment.