-
Notifications
You must be signed in to change notification settings - Fork 0
blog: list posts on the home page #39
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,3 +1,6 @@ | ||
| # Posts | ||
| public/posts.json | ||
|
|
||
| # Logs | ||
| logs | ||
| *.log | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,7 +3,7 @@ import path from 'path'; | |||||||||||||
|
|
||||||||||||||
| const postsDir = './public/posts'; | ||||||||||||||
| const distDir = './dist'; | ||||||||||||||
|
|
||||||||||||||
| const allPosts = []; | ||||||||||||||
| const years = fs.readdirSync(postsDir); | ||||||||||||||
|
|
||||||||||||||
| years.forEach(year => { | ||||||||||||||
|
|
@@ -12,23 +12,46 @@ years.forEach(year => { | |||||||||||||
| const files = fs.readdirSync(yearPath); | ||||||||||||||
| files.forEach(file => { | ||||||||||||||
| if (file.endsWith('.md')) { | ||||||||||||||
| const slug = file.replace('.md', ''); | ||||||||||||||
| const targetDir = path.join(distDir, year); | ||||||||||||||
|
|
||||||||||||||
| if (!fs.existsSync(targetDir)) { | ||||||||||||||
| fs.mkdirSync(targetDir, { | ||||||||||||||
| recursive: true | ||||||||||||||
| }); | ||||||||||||||
| } | ||||||||||||||
| fs.copyFileSync(path.join(distDir, 'index.html'), path.join(targetDir, `${slug}.html`)); | ||||||||||||||
| const fileName = file.replace('.md', ''); | ||||||||||||||
| const parts = fileName.split('-'); | ||||||||||||||
| const date = parts.slice(0, 3).join('-'); | ||||||||||||||
| const slug = parts.slice(3).join('-'); | ||||||||||||||
|
Comment on lines
+16
to
+18
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 current method of parsing the date and slug from the filename by splitting on
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| allPosts.push({ | ||||||||||||||
| year, | ||||||||||||||
| date, | ||||||||||||||
| slug, | ||||||||||||||
| originalName: fileName, | ||||||||||||||
| title: slug.replace(/-/g, ' ') | ||||||||||||||
| }); | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
| } | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| fs.copyFileSync( | ||||||||||||||
| path.join(distDir, 'index.html'), | ||||||||||||||
| path.join(distDir, '404.html') | ||||||||||||||
| ); | ||||||||||||||
| allPosts.sort((a, b) => b.date.localeCompare(a.date)); | ||||||||||||||
|
|
||||||||||||||
| const postsData = JSON.stringify(allPosts, null, 2); | ||||||||||||||
| fs.writeFileSync('./public/posts.json', postsData); | ||||||||||||||
|
|
||||||||||||||
| if (fs.existsSync(path.join(distDir, 'index.html'))) { | ||||||||||||||
| fs.writeFileSync(path.join(distDir, 'posts.json'), postsData); | ||||||||||||||
|
|
||||||||||||||
| allPosts.forEach(post => { | ||||||||||||||
| const targetDir = path.join(distDir, post.year); | ||||||||||||||
| if (!fs.existsSync(targetDir)) { | ||||||||||||||
| fs.mkdirSync(targetDir, { recursive: true }); | ||||||||||||||
| } | ||||||||||||||
| fs.copyFileSync( | ||||||||||||||
| path.join(distDir, 'index.html'), | ||||||||||||||
| path.join(targetDir, `${post.slug}.html`) | ||||||||||||||
| ); | ||||||||||||||
| }); | ||||||||||||||
|
|
||||||||||||||
| fs.copyFileSync( | ||||||||||||||
| path.join(distDir, 'index.html'), | ||||||||||||||
| path.join(distDir, '404.html') | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| console.log('✅ Build HTML from Markdown Post'); | ||||||||||||||
| console.log(`✅ Build ${allPosts.length} Posts Successfully`); | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Test7 file |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| test3 file |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| test 123 file |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| test5 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,61 +2,75 @@ import { useState, useEffect } from 'react'; | |||||||||||||||||||||||
| import ReactMarkdown from 'react-markdown'; | ||||||||||||||||||||||||
| import Analytics from './Analytics'; | ||||||||||||||||||||||||
| import NotFound from './NotFound'; | ||||||||||||||||||||||||
| import Home from './Home'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const allPostFiles = import.meta.glob('/public/posts/**/*.md', { query: '?url', import: 'default' }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| function App() { | ||||||||||||||||||||||||
| export default function App() { | ||||||||||||||||||||||||
| const [content, setContent] = useState(''); | ||||||||||||||||||||||||
| const [posts, setPosts] = useState([]); | ||||||||||||||||||||||||
| const [status, setStatus] = useState('loading'); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||
| const params = new URLSearchParams(window.location.search); | ||||||||||||||||||||||||
| const redirectedPath = params.get('p'); | ||||||||||||||||||||||||
| const currentPath = redirectedPath || window.location.pathname; | ||||||||||||||||||||||||
| let currentPath = redirectedPath || window.location.pathname; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (redirectedPath) { | ||||||||||||||||||||||||
| window.history.replaceState(null, '', redirectedPath); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (currentPath === '/' || currentPath === '/index.html') { | ||||||||||||||||||||||||
| setContent('# Welcome My Blog'); | ||||||||||||||||||||||||
| setStatus('success'); | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| fetch('/posts.json') | ||||||||||||||||||||||||
| .then(res => res.json()) | ||||||||||||||||||||||||
| .then(data => { | ||||||||||||||||||||||||
| setPosts(data); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const pathClean = currentPath.replace(/\.html$/, ''); | ||||||||||||||||||||||||
| const parts = pathClean.split('/').filter(Boolean); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const parts = currentPath.replace(/\.html$/, '').split('/').filter(Boolean); | ||||||||||||||||||||||||
| const [year, slug] = parts; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (year && slug) { | ||||||||||||||||||||||||
| const expectedPath = `/public/posts/${year}/${slug}.md`; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (allPostFiles[expectedPath]) { | ||||||||||||||||||||||||
| fetch(`/posts/${year}/${slug}.md`) | ||||||||||||||||||||||||
| .then(res => res.text()) | ||||||||||||||||||||||||
| .then(text => { | ||||||||||||||||||||||||
| setContent(text); | ||||||||||||||||||||||||
| setStatus('success'); | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| .catch(() => setStatus('404')); | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| setStatus('404'); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| setStatus('404'); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (parts.length === 0 || (parts.length === 1 && parts[0] === 'index')) { | ||||||||||||||||||||||||
| setStatus('home'); | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (parts.length === 2) { | ||||||||||||||||||||||||
| const [year, slug] = parts; | ||||||||||||||||||||||||
| const found = data.find(p => p.year === year && 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'); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||
| .catch(() => setStatus('404')); | ||||||||||||||||||||||||
|
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. 🛑 Logic Error: The fetch error handler sets status to '404', but this creates confusing user experience when the actual issue is network failure or server error, not a missing page. This misrepresents the error condition.
Suggested change
|
||||||||||||||||||||||||
| }, []); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (status === 'loading') return <div>Loading...</div>; | ||||||||||||||||||||||||
| if (status === '404') return <NotFound />; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||
| <Analytics /> | ||||||||||||||||||||||||
| <article style={{ padding: '40px', maxWidth: '800px', margin: '0 auto' }}> | ||||||||||||||||||||||||
| <ReactMarkdown>{content}</ReactMarkdown> | ||||||||||||||||||||||||
| </article> | ||||||||||||||||||||||||
| <div className="app-shell" style={{ padding: '40px', maxWidth: '800px', margin: '0 auto' }}> | ||||||||||||||||||||||||
| {status === '404' ? ( | ||||||||||||||||||||||||
| <NotFound /> | ||||||||||||||||||||||||
| ) : status === 'home' ? ( | ||||||||||||||||||||||||
| <Home posts={posts} /> | ||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||
|
Comment on lines
+62
to
+66
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. Add handling for the 'error' status state in the conditional rendering logic to properly display error messages instead of showing NotFound for network/server errors.
Suggested change
|
||||||||||||||||||||||||
| <article> | ||||||||||||||||||||||||
| <ReactMarkdown>{content}</ReactMarkdown> | ||||||||||||||||||||||||
| <hr /> | ||||||||||||||||||||||||
| <a href="/" style={{ display: 'block', marginTop: '20px' }}>← Back to Home</a> | ||||||||||||||||||||||||
|
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. Using a standard
Suggested change
|
||||||||||||||||||||||||
| </article> | ||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default App; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||
| import { useState } from 'react'; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const POSTS_PER_PAGE = 10; | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| export default function Home({ posts }) { | ||||||||||||||||||||||||||||||
| const [currentPage, setCurrentPage] = useState(0); | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| const startIndex = currentPage * POSTS_PER_PAGE; | ||||||||||||||||||||||||||||||
| const currentPosts = posts.slice(startIndex, startIndex + POSTS_PER_PAGE); | ||||||||||||||||||||||||||||||
| const hasNext = startIndex + POSTS_PER_PAGE < posts.length; | ||||||||||||||||||||||||||||||
| const hasPrev = currentPage > 0; | ||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+11
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: Add validation for posts prop to prevent runtime crash when posts is undefined or null.
Suggested change
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||
| <div className="home-container"> | ||||||||||||||||||||||||||||||
| <h1>Recent Posts</h1> | ||||||||||||||||||||||||||||||
| <ul className="post-list"> | ||||||||||||||||||||||||||||||
| {currentPosts.map(post => ( | ||||||||||||||||||||||||||||||
| <li key={post.originalName} className="post-item"> | ||||||||||||||||||||||||||||||
| <span className="post-date">{post.date}</span> | ||||||||||||||||||||||||||||||
| <a href={`/${post.year}/${post.slug}.html`} className="post-link"> | ||||||||||||||||||||||||||||||
| {post.title} | ||||||||||||||||||||||||||||||
| </a> | ||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+22
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. These post links use standard To implement this, you could pass a navigation handler function from the |
||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| <nav className="pagination"> | ||||||||||||||||||||||||||||||
| {hasPrev && ( | ||||||||||||||||||||||||||||||
| <button onClick={() => setCurrentPage(p => p - 1)}>← Newer</button> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| {hasNext && ( | ||||||||||||||||||||||||||||||
| <button onClick={() => setCurrentPage(p => p + 1)}>Older →</button> | ||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||
| </nav> | ||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
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.
🛑 Crash Risk: Add existence check before reading directory to prevent runtime crash when postsDir doesn't exist.