-
Notifications
You must be signed in to change notification settings - Fork 0
blog: code refactoring #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,125 +1,103 @@ | ||||||
| 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() { | ||||||
| const [content, setContent] = useState(''); | ||||||
| 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}`); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
| 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 <div className="app-shell">Loading...</div>; | ||||||
| } | ||||||
| const onPopState = () => { | ||||||
| fetch('/posts.json').then(res => res.json()).then(handleRouting); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛑 Crash Risk: Missing error handling in the popstate event handler. If the fetch request fails, the promise chain will result in an unhandled rejection, potentially causing runtime errors and poor user experience during browser navigation.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The handleRouting(posts); |
||||||
| }; | ||||||
|
|
||||||
| window.addEventListener('popstate', onPopState); | ||||||
| return () => window.removeEventListener('popstate', onPopState); | ||||||
| }, [handleRouting]); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To ensure the }, [handleRouting, posts]); |
||||||
|
|
||||||
| const renderContent = () => { | ||||||
| switch (status) { | ||||||
| case 'loading': return <div className="loading">Loading...</div>; | ||||||
| case '404': return <NotFound />; | ||||||
| case 'home': return <Home posts={posts} />; | ||||||
| case 'post': return <MarkdownRenderer content={content} />; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the introduction of more specific statuses (e.g., case 'about': return <MarkdownRenderer content={content} showBackLink={false} />;
case 'blogPost':return <MarkdownRenderer content={content} />; |
||||||
| default: return <NotFound />; | ||||||
| } | ||||||
| }; | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <Analytics /> | ||||||
| <div className="app-shell"> | ||||||
| <Header /> | ||||||
| {status === '404' ? ( | ||||||
| <NotFound /> | ||||||
| ) : status === 'home' ? ( | ||||||
| <Home posts={posts} /> | ||||||
| ) : ( | ||||||
| <article className="markdown-body"> | ||||||
| <ReactMarkdown | ||||||
| remarkPlugins={[remarkGfm]} | ||||||
| components={{ | ||||||
| code({ node, inline, className, children, ...props }) { | ||||||
| const match = /language-(\w+)/.exec(className || ''); | ||||||
| return !inline && match ? ( | ||||||
| <SyntaxHighlighter | ||||||
| style={vscDarkPlus} | ||||||
| language={match[1]} | ||||||
| PreTag="div" | ||||||
| {...props} | ||||||
| > | ||||||
| {String(children).replace(/\n$/, '')} | ||||||
| </SyntaxHighlighter> | ||||||
| ) : ( | ||||||
| <code className={className} {...props}> | ||||||
| {children} | ||||||
| </code> | ||||||
| ); | ||||||
| } | ||||||
| }} | ||||||
| > | ||||||
| {content} | ||||||
| </ReactMarkdown> | ||||||
| <hr /> | ||||||
| <a href="/" className="back-link">← Back to Home</a> | ||||||
| </article> | ||||||
| )} | ||||||
| <main className="main-container"> | ||||||
| {renderContent()} | ||||||
| </main> | ||||||
| <Footer /> | ||||||
| </div> | ||||||
| </> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 ( | ||
| <article className="markdown-body"> | ||
| <ReactMarkdown | ||
| remarkPlugins={[remarkGfm]} | ||
| components={{ | ||
| code({ node, inline, className, children, ...props }) { | ||
| const match = /language-(\w+)/.exec(className || ''); | ||
| return !inline && match ? ( | ||
| <SyntaxHighlighter | ||
| style={vscDarkPlus} | ||
| language={match[1]} | ||
| PreTag="div" | ||
| {...props} | ||
| > | ||
| {String(children).replace(/\n$/, '')} | ||
| </SyntaxHighlighter> | ||
| ) : ( | ||
| <code className={className} {...props}> | ||
| {children} | ||
| </code> | ||
| ); | ||
| } | ||
| }} | ||
| > | ||
| {content} | ||
| </ReactMarkdown> | ||
|
|
||
| {showBackLink && ( | ||
| <> | ||
| <hr /> | ||
| <a href="/" className="back-link">← Back to Home</a> | ||
| </> | ||
| )} | ||
| </article> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To allow for differentiated rendering in
renderContent, such as conditionally hiding the back link, consider using a more specific status for the 'about' page. For example,setStatus('about')could be used here.