Posts

This commit is contained in:
LittleSheep 2024-02-24 17:33:35 +08:00
parent 05ae2783cf
commit bd6f24c286
13 changed files with 195 additions and 22 deletions

View File

@ -34,7 +34,7 @@ export default function Home() {
</Grid>
<Grid item xs={12} md={6} sx={{ display: "flex", justifyContent: "end" }}>
<Box>
<Image src="smartsheep.svg" alt="Logo" width={256} height={256} />
<Image src="/smartsheep.svg" alt="Logo" width={256} height={256} />
</Box>
</Grid>
</Grid>

39
app/posts/[id]/page.tsx Normal file
View File

@ -0,0 +1,39 @@
import { Box, Card, CardContent, CardMedia, Divider, Typography } from "@mui/material";
import { getSinglePost } from "@/content/posts";
import Image from "next/image";
import PostContent from "@/components/posts/PostContent";
export default function PostDetailPage({ params }: { params: { id: string } }) {
const post = getSinglePost(params.id);
return (
<Card>
{
post.thumbnail &&
<CardMedia sx={{ height: 360, position: "relative" }} title={post.title}>
<Image
fill
src={post.thumbnail}
alt={post.title}
style={{ objectFit: "cover" }}
/>
</CardMedia>
}
<CardContent sx={{ paddingX: 5, paddingY: 3 }}>
<Box>
<Typography gutterBottom variant="h5" component="h1">
{post.title}
</Typography>
<Typography color="text.secondary" variant="body2">
{post.description ?? "No description yet."}
</Typography>
</Box>
<Divider sx={{ my: 5 }} />
<Box component="article" sx={{ minWidth: 0 }}>
<PostContent content={post.content ?? ""} />
</Box>
</CardContent>
</Card>
);
}

14
app/posts/layout.tsx Normal file
View File

@ -0,0 +1,14 @@
import { Box, Container } from "@mui/material";
import { ReactNode } from "react";
export default function PostLayout({children}: Readonly<{
children: ReactNode;
}>) {
return (
<Container sx={{ display: "flex", justifyContent: "center", gap: 4, py: 4 }}>
<Box sx={{ flexGrow: 1, maxWidth: 720 }}>
{children}
</Box>
</Container>
)
}

40
app/posts/page.tsx Normal file
View File

@ -0,0 +1,40 @@
import { Button, Card, CardActions, CardContent, CardMedia, Typography } from "@mui/material";
import { getSortedPosts } from "@/content/posts";
import Image from "next/image";
import Link from "next/link";
export default function PostList() {
const posts = getSortedPosts();
return (
posts.map((post) => (
<Card key={post.id} sx={{ width: "100%" }}>
{
post.thumbnail &&
<CardMedia sx={{ height: 160, position: "relative" }} title={post.title}>
<Image
fill
src={post.thumbnail}
alt={post.title}
style={{ objectFit: "cover" }}
/>
</CardMedia>
}
<CardContent sx={{ paddingX: 5, paddingY: 3 }}>
<Typography gutterBottom variant="h5" component="h2">
{post.title}
</Typography>
<Typography variant="body2" color="text.secondary">
{post.description ?? "No description yet."}
</Typography>
</CardContent>
<CardActions sx={{ paddingX: 4, paddingBottom: 2 }}>
<Link href={`/posts/${post.id}`} passHref>
<Button>Read more</Button>
</Link>
</CardActions>
</Card>
))
);
}

BIN
bun.lockb

Binary file not shown.

View File

@ -15,6 +15,7 @@ import { SITE_NAME } from "@/app/consts";
import NavigationDrawer, { DRAWER_WIDTH, AppNavigationHeader, isMobileQuery } from "@/components/NavigationDrawer";
import MenuIcon from "@mui/icons-material/Menu";
import Image from "next/image";
import Link from "next/link";
function HideOnScroll(props: {
window?: () => Window;
@ -105,11 +106,13 @@ export default function AppShell({ children }: {
aria-label="menu"
sx={{ ml: isMobile ? 0.5 : 0, mr: 2 }}
>
<Image src="smartsheep.svg" alt="Logo" width={32} height={32} />
<Image src="/smartsheep.svg" alt="Logo" width={32} height={32} />
</IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
<Link href="/">
{SITE_NAME}
</Link>
</Typography>
<IconButton

View File

@ -2,15 +2,12 @@
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import InboxIcon from "@mui/icons-material/MoveToInbox";
import MailIcon from "@mui/icons-material/Mail";
import {
Box,
Divider,
Drawer,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
@ -20,6 +17,7 @@ import {
import { theme } from "@/app/theme";
import { ReactNode } from "react";
import HomeIcon from "@mui/icons-material/Home";
import ArticleIcon from "@mui/icons-material/Article";
import Link from "next/link";
export interface NavigationItem {
@ -30,7 +28,8 @@ export interface NavigationItem {
export const DRAWER_WIDTH = 320;
export const NAVIGATION_ITEMS: NavigationItem[] = [
{ icon: <HomeIcon />, title: "首页", link: "/" }
{ icon: <HomeIcon />, title: "首页", link: "/" },
{ icon: <ArticleIcon />, title: "新闻", link: "/posts" },
];
export const AppNavigationHeader = styled("div")(({ theme }) => ({

View File

@ -0,0 +1,7 @@
"use client";
import MuiMarkdown from "mui-markdown";
export default function PostContent({ content }: { content: string }) {
return <MuiMarkdown>{content}</MuiMarkdown>;
}

47
content/posts.ts Normal file
View File

@ -0,0 +1,47 @@
import fs from "fs";
import path from "path";
import matter from "gray-matter";
const postsDirectory = path.join(process.cwd(), "content", "posts");
export interface Post {
id: string;
title: string;
thumbnail?: string;
description?: string;
content?: string;
date: Date;
}
export function getSortedPosts() {
const fileNames = fs.readdirSync(postsDirectory);
const allPostsData: Post[] = fileNames.map((fileName) => {
const id = fileName.replace(/\.md$/, "");
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, "utf8");
const matterResult = matter(fileContents);
return {
id,
...matterResult.data
} as Post;
});
return allPostsData.sort((a, b) => {
return a.date < b.date ? 1 : -1;
});
}
export function getSinglePost(id: string) {
const fullPath = path.join(postsDirectory, id + ".md");
const fileContents = fs.readFileSync(fullPath, "utf8");
const matterResult = matter(fileContents);
return {
id,
content: matterResult.content,
...matterResult.data,
} as Post;
}

14
content/posts/initial.md Normal file
View File

@ -0,0 +1,14 @@
---
thumbnail: 'https://images.unsplash.com/photo-1707344088547-3cf7cea5ca49?q=80&w=2970&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D'
title: 'Two Forms of Pre-rendering'
date: '2020-01-01'
---
# Woah!
Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.

View File

@ -1,4 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
/** @type {import("next").NextConfig} */
const nextConfig = {
pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
}
};
export default nextConfig;

View File

@ -13,23 +13,32 @@
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.8",
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@mui/material-nextjs": "^5.15.11",
"@next/mdx": "^14.1.0",
"@types/mdx": "^2.0.11",
"gray-matter": "^4.0.3",
"html-react-parser": "^5.1.7",
"marked": "^12.0.0",
"mui-markdown": "^1.1.13",
"next": "14.1.0",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"typescript": "^5",
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"eslint": "^8",
"eslint-config-next": "14.1.0"
"typescript": "^5"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",

View File

@ -6,15 +6,6 @@ const config: Config = {
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
},
},
plugins: [],
plugins: [require("@tailwindcss/typography")],
};
export default config;