Complete WIP: Architecture refactor.
Mount JSX server side templating for blog posts. Send AppShell conditionally. Maintain support for HMR via HTMLbundles using Bun's native fullstack dev server under an /hmr path. This is only mounted in development and is supported by the onImport Bun plugin. Add DB creation on startup and load pages based on those records.
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ThemePicker } from "../components/theme-picker";
|
||||
|
||||
export function Blog() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Blog</h1>
|
||||
<a href="/">Home</a>
|
||||
<ThemePicker />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,143 @@
|
||||
import React from 'react';
|
||||
import { getRecentPosts, formatDate, calculateReadTime, getNumOfPosts } from '../../db/posts';
|
||||
import { parseTags } from '../../db/tags';
|
||||
import { type BlogPost } from '../../db/queries';
|
||||
|
||||
export function Home() {
|
||||
return (
|
||||
<main>
|
||||
<h1>Home!</h1>
|
||||
<a href="/blog">Blog</a>
|
||||
</main>
|
||||
)
|
||||
export function Home({ searchParams }: { searchParams: Record<string, string> }) {
|
||||
const currentPage = parseInt(searchParams.page || "1", 10);
|
||||
const totalPages = Math.ceil(getNumOfPosts() / 10);
|
||||
const posts = getRecentPosts(10, ); // Get the 10 most recent posts
|
||||
|
||||
return (
|
||||
<main>
|
||||
<div className="posts-list">
|
||||
{posts.length > 0 ? (
|
||||
posts.map((post) => (
|
||||
<PostCard key={post.id} post={post} />
|
||||
))
|
||||
) : (
|
||||
<div className="empty-state">
|
||||
<h2>No posts yet</h2>
|
||||
<p>Check back soon for new blog posts!</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Pagination currentPage={currentPage} totalPages={totalPages} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
interface PostCardProps {
|
||||
post: BlogPost;
|
||||
}
|
||||
|
||||
function PostCard({ post }: PostCardProps) {
|
||||
const tags = parseTags(post.tags);
|
||||
const formattedDate = formatDate(post.date);
|
||||
|
||||
return (
|
||||
<article className="post-card">
|
||||
<header className="post-card-header">
|
||||
<h2 className="post-card-title">
|
||||
<a href={`${post.path}`} className="post-card-link">{post.title}</a>
|
||||
</h2>
|
||||
<div className="post-card-meta">
|
||||
<time dateTime={post.date}>{formattedDate}</time>
|
||||
<span className="meta-separator">•</span>
|
||||
<span className="read-time">5 min read</span>
|
||||
</div>
|
||||
{tags.length > 0 && (
|
||||
<div className="post-card-tags">
|
||||
{tags.map((tag, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={`/?tag=${tag.toLowerCase().replace(/\s+/g, '-')}`}
|
||||
className="post-tag"
|
||||
>
|
||||
{tag}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
{post.summary && (
|
||||
<div className="post-card-summary">
|
||||
<p>{post.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
<footer className="post-card-footer">
|
||||
<a href={`${post.path}`} className="read-more-link">Read more →</a>
|
||||
</footer>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function Pagination({ currentPage, totalPages }: { currentPage: number; totalPages: number }) {
|
||||
// Calculate the range of page numbers to show
|
||||
let startPage: number;
|
||||
let endPage: number;
|
||||
|
||||
if (totalPages <= 9) {
|
||||
// If there are fewer than 9 pages, show all of them
|
||||
startPage = 1;
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
// If we have more than 9 pages, calculate the window
|
||||
if (currentPage <= 5) {
|
||||
// When we're at the start, show pages 1-9
|
||||
startPage = 1;
|
||||
endPage = 9;
|
||||
} else if (currentPage >= totalPages - 4) {
|
||||
// When we're near the end, show the last 9 pages
|
||||
startPage = totalPages - 8;
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
// Otherwise, center the window around the current page
|
||||
startPage = currentPage - 4;
|
||||
endPage = currentPage + 4;
|
||||
}
|
||||
}
|
||||
|
||||
const pages = Array.from({ length: endPage - startPage + 1 }, (_, i) => startPage + i);
|
||||
|
||||
return (
|
||||
<nav className="pagination">
|
||||
{/* Previous button - always visible, disabled on first page */}
|
||||
<a
|
||||
href={`/?page=${currentPage - 1}`}
|
||||
className={`pagination-link ${currentPage === 1 ? 'disabled' : ''}`}
|
||||
style={{
|
||||
opacity: currentPage === 1 ? 0.5 : 1,
|
||||
cursor: currentPage === 1 ? 'not-allowed' : 'pointer',
|
||||
pointerEvents: currentPage === 1 ? 'none' : 'auto'
|
||||
}}
|
||||
>
|
||||
Previous
|
||||
</a>
|
||||
|
||||
{/* Page numbers */}
|
||||
{pages.map((page) => (
|
||||
<a
|
||||
key={page}
|
||||
href={`/?page=${page}`}
|
||||
className={`pagination-link ${page === currentPage ? 'active' : ''}`}
|
||||
>
|
||||
{page}
|
||||
</a>
|
||||
))}
|
||||
|
||||
{/* Next button - always visible, disabled on last page */}
|
||||
<a
|
||||
href={`/?page=${currentPage + 1}`}
|
||||
className={`pagination-link ${currentPage === totalPages ? 'disabled' : ''}`}
|
||||
style={{
|
||||
opacity: currentPage === totalPages ? 0.5 : 1,
|
||||
cursor: currentPage === totalPages ? 'not-allowed' : 'pointer',
|
||||
pointerEvents: currentPage === totalPages ? 'none' : 'auto'
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</a>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
45
src/frontend/pages/post.tsx
Normal file
45
src/frontend/pages/post.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
|
||||
interface PostProps {
|
||||
// HTML string for the blog post body
|
||||
children: string;
|
||||
meta: {
|
||||
title: string;
|
||||
date: Date;
|
||||
readingTime: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function Post({ children, meta }: PostProps) {
|
||||
return (
|
||||
<main>
|
||||
<article className="blog-post">
|
||||
<header className="post-header">
|
||||
<h1>{meta.title}</h1>
|
||||
<div className="post-meta">
|
||||
{meta.date && meta.date instanceof Date &&
|
||||
<>
|
||||
|
||||
<time dateTime={meta.date.toISOString()}>
|
||||
{meta.date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})}
|
||||
</time>
|
||||
</>
|
||||
}
|
||||
|
||||
{meta.readingTime &&
|
||||
<>
|
||||
<span className="meta-separator">•</span>
|
||||
<span>{meta.readingTime}</span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
<div className="post-content" dangerouslySetInnerHTML={{ __html: children }} />
|
||||
</article>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user