Compare commits

...

2 Commits

Author SHA1 Message Date
86bb1d349a RSS & Sitemap 2024-02-24 18:54:47 +08:00
ea50f739b3 💄 Better article 2024-02-24 18:29:29 +08:00
15 changed files with 125 additions and 40 deletions

View File

@ -11,6 +11,8 @@ export interface RelatedAccount {
} }
export const SITE_NAME = "Goatshed"; export const SITE_NAME = "Goatshed";
export const SITE_DESCRIPTION = "山羊寒舍,在这里最终智羊工作室的最新动态。";
export const SITE_URL = "https://smartsheep.studio"
export const RELATED_ACCOUNTS: RelatedAccount[] = [ export const RELATED_ACCOUNTS: RelatedAccount[] = [
{ icon: <GitHubIcon />, platform: "GitHub", accountName: "@smartsheep-hq", link: "https://github.com/smartsheep-hq" }, { icon: <GitHubIcon />, platform: "GitHub", accountName: "@smartsheep-hq", link: "https://github.com/smartsheep-hq" },

28
app/feed/route.ts Normal file
View File

@ -0,0 +1,28 @@
import RSS from "rss";
import { SITE_DESCRIPTION, SITE_NAME, SITE_URL } from "@/app/consts";
import { getSortedPosts } from "@/content/posts";
export async function GET() {
const feed = new RSS({
title: SITE_NAME,
description: SITE_DESCRIPTION,
site_url: SITE_URL,
feed_url: `${SITE_URL}/feed`,
language: "zh-CN"
});
getSortedPosts().forEach((item) => {
feed.item({
url: `${SITE_URL}/p/${item.id}`,
title: item.title,
description: item.description ?? "No description yet.",
date: item.date,
});
});
return new Response(feed.xml(), {
headers: {
"content-type": "application/xml"
}
});
}

View File

@ -3,7 +3,7 @@ import { ReactNode } from "react";
import { ThemeProvider } from "@mui/material/styles"; import { ThemeProvider } from "@mui/material/styles";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter"; import { AppRouterCacheProvider } from "@mui/material-nextjs/v13-appRouter";
import { CssBaseline } from "@mui/material"; import { CssBaseline } from "@mui/material";
import { SITE_NAME } from "@/app/consts"; import { SITE_DESCRIPTION, SITE_NAME } from "@/app/consts";
import { theme } from "@/app/theme"; import { theme } from "@/app/theme";
import "@fontsource/roboto/300.css"; import "@fontsource/roboto/300.css";
@ -16,15 +16,18 @@ import "./globals.css";
import AppShell from "@/components/AppShell"; import AppShell from "@/components/AppShell";
export const metadata: Metadata = { export const metadata: Metadata = {
title: SITE_NAME, title: {
description: "山羊寒舍,在这里最终智羊工作室的最新动态。" default: SITE_NAME,
template: `${SITE_NAME} | %s`
},
description: SITE_DESCRIPTION,
}; };
export default function RootLayout({ children }: Readonly<{ export default function RootLayout({ children }: Readonly<{
children: ReactNode; children: ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <html lang="zh-CN">
<body> <body>
<AppRouterCacheProvider> <AppRouterCacheProvider>
<CssBaseline /> <CssBaseline />

View File

@ -24,8 +24,8 @@ export default function Home() {
alignItems="center" alignItems="center"
sx={{ height: "calc(100vh - 64px)" }} sx={{ height: "calc(100vh - 64px)" }}
> >
<Grid item xs={12} md={6} sx={{ textAlign: { xs: "center", md: "initial" } }}> <Grid item xs={12} sm={6} sx={{ textAlign: { xs: "center", sm: "initial" } }}>
<Typography variant="h3" component="h1" gutterBottom> 👋</Typography> <Typography variant="h1" gutterBottom> 👋</Typography>
<Typography paragraph> <Typography paragraph>
SmartSheep Studio SmartSheep Studio
@ -35,8 +35,8 @@ export default function Home() {
<Grid <Grid
item item
xs={12} xs={12}
md={6} sm={6}
sx={{ display: "flex", justifyContent: { xs: "center", lg: "end" }, order: { xs: -100, lg: 0 } }} sx={{ display: "flex", justifyContent: { xs: "center", sm: "end" }, order: { xs: -100, sm: 0 } }}
> >
<Box> <Box>
<Image src="/smartsheep.svg" alt="Logo" width={256} height={256} /> <Image src="/smartsheep.svg" alt="Logo" width={256} height={256} />
@ -50,8 +50,8 @@ export default function Home() {
alignItems="center" alignItems="center"
sx={{ height: "calc(100vh - 64px)" }} sx={{ height: "calc(100vh - 64px)" }}
> >
<Grid item xs={12} md={6} sx={{ display: "flex", justifyContent: { xs: "center", lg: "end" } }}> <Grid item xs={12} sm={6} sx={{ display: "flex", justifyContent: { xs: "center", sm: "end" } }}>
<Card sx={{ flexGrow: 1, mr: { xs: 0, md: 8 } }}> <Card sx={{ flexGrow: 1, mr: { xs: 0, sm: 4, md: 8 } }}>
<List sx={{ width: "100%", bgcolor: "background.paper" }}> <List sx={{ width: "100%", bgcolor: "background.paper" }}>
{RELATED_ACCOUNTS.map((item, idx) => ( {RELATED_ACCOUNTS.map((item, idx) => (
<Link key={idx} href={item.link} target="_blank" passHref> <Link key={idx} href={item.link} target="_blank" passHref>
@ -66,8 +66,8 @@ export default function Home() {
</List> </List>
</Card> </Card>
</Grid> </Grid>
<Grid item xs={12} md={6} sx={{ textAlign: { xs: "center", md: "initial" } }}> <Grid item xs={12} sm={6} sx={{ textAlign: { xs: "center", sm: "initial" } }}>
<Typography variant="h3" component="h1" gutterBottom></Typography> <Typography variant="h1" gutterBottom></Typography>
<Typography paragraph> <Typography paragraph>
2019 2019

View File

@ -22,7 +22,7 @@ export default function PostDetailPage({ params }: { params: { id: string } }) {
<CardContent sx={{ paddingX: 5, paddingY: 3 }}> <CardContent sx={{ paddingX: 5, paddingY: 3 }}>
<Box> <Box>
<Typography gutterBottom variant="h5" component="h1"> <Typography gutterBottom variant="h2">
{post.title} {post.title}
</Typography> </Typography>
<Typography color="text.secondary" variant="body2"> <Typography color="text.secondary" variant="body2">
@ -30,7 +30,7 @@ export default function PostDetailPage({ params }: { params: { id: string } }) {
</Typography> </Typography>
</Box> </Box>
<Divider sx={{ my: 5 }} /> <Divider sx={{ my: 5 }} />
<Box component="article" sx={{ minWidth: 0 }}> <Box component="article" className="prose max-w-none" sx={{ minWidth: 0 }}>
<PostContent content={post.content ?? ""} /> <PostContent content={post.content ?? ""} />
</Box> </Box>
</CardContent> </CardContent>

View File

@ -5,7 +5,7 @@ export default function PostLayout({children}: Readonly<{
children: ReactNode; children: ReactNode;
}>) { }>) {
return ( return (
<Container sx={{ display: "flex", justifyContent: "center", gap: 4, py: 4 }}> <Container sx={{ display: "flex", justifyContent: "center", gap: 4, py: 2 }}>
<Box sx={{ flexGrow: 1, maxWidth: 720 }}> <Box sx={{ flexGrow: 1, maxWidth: 720 }}>
{children} {children}
</Box> </Box>

View File

@ -22,7 +22,7 @@ export default function PostList() {
} }
<CardContent sx={{ paddingX: 5, paddingY: 3 }}> <CardContent sx={{ paddingX: 5, paddingY: 3 }}>
<Typography gutterBottom variant="h5" component="h2"> <Typography gutterBottom variant="h3">
{post.title} {post.title}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
@ -30,7 +30,7 @@ export default function PostList() {
</Typography> </Typography>
</CardContent> </CardContent>
<CardActions sx={{ paddingX: 4, paddingBottom: 2 }}> <CardActions sx={{ paddingX: 4, paddingBottom: 2 }}>
<Link href={`/posts/${post.id}`} passHref> <Link href={`/p/${post.id}`} passHref>
<Button>Read more</Button> <Button>Read more</Button>
</Link> </Link>
</CardActions> </CardActions>

29
app/sitemap.ts Normal file
View File

@ -0,0 +1,29 @@
import { MetadataRoute } from "next";
import { getSortedPosts, Post } from "@/content/posts";
import { SITE_URL } from "@/app/consts";
export default function sitemap(): MetadataRoute.Sitemap {
const posts = getSortedPosts();
return [
{
url: `${SITE_URL}/`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1
},
{
url: `${SITE_URL}/posts`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.8
},
...posts.map((item: Post) => ({
url: `${SITE_URL}/posts/${item.id}`,
lastModified: item.date,
changeFrequency: "daily" as any,
priority: 0.75
}))
];
}

View File

@ -1,4 +1,4 @@
"use client" "use client";
import { createTheme } from "@mui/material/styles"; import { createTheme } from "@mui/material/styles";
@ -10,5 +10,13 @@ export const theme = createTheme({
secondary: { secondary: {
main: "#d43630" main: "#d43630"
} }
} },
typography: {
h1: { fontSize: "2.5rem" },
h2: { fontSize: "2rem" },
h3: { fontSize: "1.75rem" },
h4: { fontSize: "1.5rem" },
h5: { fontSize: "1.25rem" },
h6: { fontSize: "1.15rem" },
},
}); });

BIN
bun.lockb

Binary file not shown.

View File

@ -109,7 +109,7 @@ export default function AppShell({ children }: {
<Image src="/smartsheep.svg" alt="Logo" width={32} height={32} /> <Image src="/smartsheep.svg" alt="Logo" width={32} height={32} />
</IconButton> </IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> <Typography variant="h6" component="div" sx={{ flexGrow: 1, fontSize: "1.2rem" }}>
<Link href="/"> <Link href="/">
{SITE_NAME} {SITE_NAME}
</Link> </Link>

View File

@ -18,18 +18,22 @@ import { theme } from "@/app/theme";
import { ReactNode } from "react"; import { ReactNode } from "react";
import HomeIcon from "@mui/icons-material/Home"; import HomeIcon from "@mui/icons-material/Home";
import ArticleIcon from "@mui/icons-material/Article"; import ArticleIcon from "@mui/icons-material/Article";
import FeedIcon from "@mui/icons-material/RssFeed";
import Link from "next/link"; import Link from "next/link";
export interface NavigationItem { export interface NavigationItem {
icon: ReactNode; icon?: ReactNode;
title: string; title?: string;
link: string; link?: string;
divider?: boolean;
} }
export const DRAWER_WIDTH = 320; export const DRAWER_WIDTH = 320;
export const NAVIGATION_ITEMS: NavigationItem[] = [ export const NAVIGATION_ITEMS: NavigationItem[] = [
{ icon: <HomeIcon />, title: "首页", link: "/" }, { icon: <HomeIcon />, title: "首页", link: "/" },
{ icon: <ArticleIcon />, title: "新闻", link: "/posts" }, { icon: <ArticleIcon />, title: "新闻", link: "/posts" },
{ divider: true },
{ icon: <FeedIcon />, title: "订阅源", link: "/feed" },
]; ];
export const AppNavigationHeader = styled("div")(({ theme }) => ({ export const AppNavigationHeader = styled("div")(({ theme }) => ({
@ -57,14 +61,16 @@ export function AppNavigation({ showClose, onClose }: {
</AppNavigationHeader> </AppNavigationHeader>
<Divider /> <Divider />
<List> <List>
{NAVIGATION_ITEMS.map((item, idx) => ( {NAVIGATION_ITEMS.map((item, idx) => {
<Link key={idx} href={item.link} passHref> return item.divider ? <Divider /> : (
<ListItemButton> <Link key={idx} href={item.link ?? "/"} passHref>
<ListItemIcon>{item.icon}</ListItemIcon> <ListItemButton>
<ListItemText primary={item.title} /> <ListItemIcon>{item.icon}</ListItemIcon>
</ListItemButton> <ListItemText primary={item.title} />
</Link> </ListItemButton>
))} </Link>
);
})}
</List> </List>
</> </>
); );

View File

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

View File

@ -5,9 +5,18 @@ const nextConfig = {
remotePatterns: [ remotePatterns: [
{ {
protocol: "https", protocol: "https",
hostname: "**", hostname: "**"
}, }
], ]
},
async rewrites() {
return [
{ source: "/rss", destination: "/feed" },
{ source: "/rss.xml", destination: "/feed.xml" },
{ source: "/feed.xml", destination: "/feed" },
{ source: "/p/:id", destination: "/posts/:id" }
];
} }
}; };

View File

@ -21,18 +21,18 @@
"@next/mdx": "^14.1.0", "@next/mdx": "^14.1.0",
"@types/mdx": "^2.0.11", "@types/mdx": "^2.0.11",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"html-react-parser": "^5.1.7",
"marked": "^12.0.0",
"mui-markdown": "^1.1.13",
"next": "14.1.0", "next": "14.1.0",
"react": "^18", "react": "^18",
"react-dom": "^18" "react-dom": "^18",
"react-markdown": "^9.0.1",
"rss": "^1.2.2"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@types/rss": "^0.0.32",
"autoprefixer": "^10.0.1", "autoprefixer": "^10.0.1",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.1.0", "eslint-config-next": "14.1.0",