Skip to content

Commit

Permalink
feat: Add dynamic categories and tags pages with enhanced header inte…
Browse files Browse the repository at this point in the history
…raction #18 (#23)

1. Enable dynamic categories pages based on user-configured categories (links, titles, icons) in config.yml.
2. Generate dynamic tags pages based on post tags, with Chinese tags converted to Pinyin and underscores in URLs.
3. Move type interfaces from getConfig to global types for better type management.
4. Implement CategoryLinks.tsx and TagLinks.tsx to link categories and tags in posts, making thumbnails, titles, and categories clickable.
5. Display categories and tags at the top of each post for better navigation.
6. Improve header accessibility and desktop hover interaction for the "Posts" menu to show user-defined categories, with indentation for mobile displays.
7. Refactor services folder structure, add subfolders, and update imports accordingly.
  • Loading branch information
ZL-Asica authored Oct 22, 2024
1 parent f6ee3fa commit b36a6c0
Show file tree
Hide file tree
Showing 30 changed files with 648 additions and 212 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
49 changes: 49 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"highlight.js": "^11.10.0",
"marked": "^14.1.3",
"next": "14.2.15",
"pinyin": "^4.0.0-alpha.2",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.3.0",
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>
);
}
45 changes: 45 additions & 0 deletions src/app/tags/[tagSlug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getAllPosts } from '@/services/content/posts';
import { notFound } from 'next/navigation';
import PostListLayout from '@/components/layout/PostListLayout';
import { getUniqueTags, convertToPinyin } from '@/services/parsing/tagLinks';

// Generate static paths for all unique tags
export async function generateStaticParams() {
const uniqueTags = await getUniqueTags();
return uniqueTags.map((tag) => ({
// Convert only Chinese tags to pinyin slug
tagSlug: convertToPinyin(tag),
}));
}

export default async function TagPage({
params,
}: {
params: { tagSlug: string };
}) {
const posts = await getAllPosts();

// Retrieve all unique tags from the posts
const uniqueTags = await getUniqueTags();

// Find the tag based on the slug from params
const tag = uniqueTags.find((tag) => convertToPinyin(tag) === params.tagSlug);

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

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

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

<PostListLayout posts={filteredPosts} />
</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 b36a6c0

Please sign in to comment.