From da124e78cdf4656399af85aab62aede91b4c8d1f Mon Sep 17 00:00:00 2001 From: gapry Date: Fri, 6 Mar 2026 08:09:02 +0800 Subject: [PATCH] blog: code refactoring --- src/App.jsx | 156 ++++++++++++---------------- src/components/MarkdownRenderer.jsx | 42 ++++++++ 2 files changed, 109 insertions(+), 89 deletions(-) create mode 100644 src/components/MarkdownRenderer.jsx diff --git a/src/App.jsx b/src/App.jsx index 1ca31e8..5cc569e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,13 +1,11 @@ -import { useState, useEffect } from 'react'; -import ReactMarkdown from 'react-markdown'; -import remarkGfm from 'remark-gfm'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { useState, useEffect, useCallback } from 'react'; import Analytics from './components/Analytics'; import NotFound from './pages/NotFound/NotFound'; import Home from './pages/Home/Home'; import Header from './components/Header/Header'; import Footer from './components/Footer/Footer'; +import MarkdownRenderer from './components/MarkdownRenderer'; +import siteConfig from './data/config.json'; import './styles/App.css'; export default function App() { @@ -15,111 +13,91 @@ export default function App() { const [posts , setPosts] = useState([]); const [status , setStatus] = useState('loading'); - useEffect(() => { + const fetchMarkdown = useCallback((url, title) => { + fetch(url) + .then(res => res.text()) + .then(text => { + document.title = title; + setContent(text); + setStatus('post'); + }) + .catch(() => setStatus('404')); + }, []); + + const handleRouting = useCallback((allPosts) => { const params = new URLSearchParams(window.location.search); const redirectedPath = params.get('p'); - - let currentPath = redirectedPath || window.location.pathname; + const currentPath = redirectedPath || window.location.pathname; if (redirectedPath) { window.history.replaceState(null, '', redirectedPath); } - fetch('/posts.json') - .then(res => res.json()) - .then(data => { - setPosts(data); + const pathClean = currentPath.replace(/\.html$/, ''); + const parts = pathClean.split('/').filter(Boolean); + + if (parts.length === 0 || (parts.length === 1 && parts[0] === 'index')) { + document.title = siteConfig.siteName; + setStatus('home'); + return; + } - const pathClean = currentPath.replace(/\.html$/, ''); - const parts = pathClean.split('/').filter(Boolean); + if (parts.length === 1 && parts[0] === 'about') { + fetchMarkdown('/about.md', `About | ${siteConfig.siteName}`); + return; + } - if (parts.length === 1 && parts[0] === 'about') { - fetch('/about.md') - .then(res => res.text()) - .then(text => { - setContent(text); - setStatus('post'); - }) - .catch(() => setStatus('404')); - return; - } + if (parts.length === 4) { + const [year, month, day, slug] = parts; - if (parts.length === 0 || (parts.length === 1 && parts[0] === 'index')) { - setStatus('home'); - return; - } + const found = allPosts.find(p => + p.year === year && p.month === month && p.day === day && p.slug === slug + ); - if (parts.length === 4) { - const [year, month, day, slug] = parts; + if (found) { + fetchMarkdown(`/posts/${year}/${found.originalName}.md`, `${found.title} | ${siteConfig.siteName}`); + return; + } + } + + setStatus('404'); + }, [fetchMarkdown]); - const found = data.find(p => - p.year === year && - p.month === month && - p.day === day && - p.slug === slug - ); - - if (found) { - fetch(`/posts/${year}/${found.originalName}.md`) - .then(res => res.text()) - .then(text => { - setContent(text); - setStatus('post'); - }) - .catch(() => setStatus('404')); - } else { - setStatus('404'); - } - } else { - setStatus('404'); - } + useEffect(() => { + fetch('/posts.json') + .then(res => res.json()) + .then(data => { + setPosts(data); + handleRouting(data); }) .catch(() => setStatus('404')); - }, []); - if (status === 'loading') { - return
Loading...
; - } + const onPopState = () => { + fetch('/posts.json').then(res => res.json()).then(handleRouting); + }; + + window.addEventListener('popstate', onPopState); + return () => window.removeEventListener('popstate', onPopState); + }, [handleRouting]); + + const renderContent = () => { + switch (status) { + case 'loading': return
Loading...
; + case '404': return ; + case 'home': return ; + case 'post': return ; + default: return ; + } + }; return ( <>
- {status === '404' ? ( - - ) : status === 'home' ? ( - - ) : ( -
- - {String(children).replace(/\n$/, '')} - - ) : ( - - {children} - - ); - } - }} - > - {content} - -
- ← Back to Home -
- )} +
+ {renderContent()} +
diff --git a/src/components/MarkdownRenderer.jsx b/src/components/MarkdownRenderer.jsx new file mode 100644 index 0000000..d57c672 --- /dev/null +++ b/src/components/MarkdownRenderer.jsx @@ -0,0 +1,42 @@ +import ReactMarkdown from 'react-markdown'; +import remarkGfm from 'remark-gfm'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; + +export default function MarkdownRenderer({ content, showBackLink = true }) { + return ( +
+ + {String(children).replace(/\n$/, '')} + + ) : ( + + {children} + + ); + } + }} + > + {content} + + + {showBackLink && ( + <> +
+ ← Back to Home + + )} +
+ ); +}