mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-04 00:06:22 +00:00
- Updated `getPostById` function to accept an optional authorization token. - Modified `PostPage` to retrieve the token from cookies and pass it to the API call. - Added smooth transition effects for background and text colors in `globals.css`. - Cleaned up imports and formatting in `blog-home.tsx`. - Refactored `blog-post.tsx` to use `MDXRemote` for rendering markdown content. - Introduced `blog-comment.tsx` and `blog-post-header.client.tsx` components for better structure. - Added a switch component for dark/light mode toggle in the navbar. - Updated `Post` model to include a description field.
184 lines
7.0 KiB
TypeScript
184 lines
7.0 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import Link from "next/link"
|
|
|
|
import {
|
|
NavigationMenu,
|
|
NavigationMenuContent,
|
|
NavigationMenuItem,
|
|
NavigationMenuLink,
|
|
NavigationMenuList,
|
|
NavigationMenuTrigger,
|
|
navigationMenuTriggerStyle,
|
|
} from "@/components/ui/navigation-menu"
|
|
import GravatarAvatar from "@/components/gravatar"
|
|
import { useDevice } from "@/contexts/device-context"
|
|
import config from "@/config"
|
|
import { useState, useEffect } from "react"
|
|
import { Sheet, SheetContent, SheetTitle, SheetTrigger } from "@/components/ui/sheet"
|
|
import { Menu } from "lucide-react"
|
|
import { Switch } from "./ui/switch"
|
|
|
|
const navbarMenuComponents = [
|
|
{
|
|
title: "首页",
|
|
href: "/"
|
|
},
|
|
{
|
|
title: "文章",
|
|
children: [
|
|
{ title: "归档", href: "/archives" },
|
|
{ title: "标签", href: "/labels" },
|
|
{ title: "随机", href: "/random" }
|
|
]
|
|
},
|
|
{
|
|
title: "页面",
|
|
children: [
|
|
{ title: "关于我", href: "/about" },
|
|
{ title: "联系我", href: "/contact" },
|
|
{ title: "友链", href: "/links" },
|
|
{ title: "隐私政策", href: "/privacy-policy" },
|
|
]
|
|
}
|
|
]
|
|
|
|
export function Navbar() {
|
|
const { navbarAdditionalClassName, setMode, mode } = useDevice()
|
|
return (
|
|
<nav className={`grid grid-cols-[1fr_auto_1fr] items-center gap-4 h-16 px-4 w-full ${navbarAdditionalClassName}`}>
|
|
<div className="flex items-center justify-start">
|
|
<span className="font-bold truncate">{config.metadata.name}</span>
|
|
</div>
|
|
<div className="flex items-center justify-center">
|
|
<NavMenuCenter />
|
|
</div>
|
|
<div className="flex items-center justify-end space-x-2">
|
|
<Switch checked={mode === "dark"} onCheckedChange={(checked) => setMode(checked ? "dark" : "light")} />
|
|
<GravatarAvatar email="snowykami@outlook.com" />
|
|
<SidebarMenuClientOnly />
|
|
</div>
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
function NavMenuCenter() {
|
|
const { isMobile } = useDevice()
|
|
if (isMobile) return null
|
|
return (
|
|
<NavigationMenu viewport={false}>
|
|
<NavigationMenuList className="flex space-x-1">
|
|
{navbarMenuComponents.map((item) => (
|
|
<NavigationMenuItem key={item.title}>
|
|
{item.href ? (
|
|
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
|
|
<Link href={item.href} className="flex items-center gap-1 font-extrabold">
|
|
{item.title}
|
|
</Link>
|
|
</NavigationMenuLink>
|
|
) : item.children ? (
|
|
<>
|
|
<NavigationMenuTrigger className="flex items-center gap-1 font-extrabold">
|
|
{item.title}
|
|
</NavigationMenuTrigger>
|
|
<NavigationMenuContent>
|
|
<ul className="grid gap-2 p-0 min-w-[200px] max-w-[600px] grid-cols-[repeat(auto-fit,minmax(120px,1fr))]">
|
|
{item.children.map((child) => (
|
|
<ListItem
|
|
key={child.title}
|
|
title={child.title}
|
|
href={child.href}
|
|
/>
|
|
))}
|
|
</ul>
|
|
</NavigationMenuContent>
|
|
</>
|
|
) : null}
|
|
</NavigationMenuItem>
|
|
))}
|
|
</NavigationMenuList>
|
|
</NavigationMenu>
|
|
)
|
|
}
|
|
|
|
function ListItem({
|
|
title,
|
|
children,
|
|
href,
|
|
...props
|
|
}: React.ComponentPropsWithoutRef<"li"> & { href: string }) {
|
|
return (
|
|
<li {...props} className="flex justify-center">
|
|
<NavigationMenuLink asChild>
|
|
<Link href={href} className="flex flex-col items-center text-center w-full">
|
|
<div className="text-sm leading-none font-medium">{title}</div>
|
|
<p className="text-muted-foreground line-clamp-2 text-sm leading-snug">
|
|
{children}
|
|
</p>
|
|
</Link>
|
|
</NavigationMenuLink>
|
|
</li>
|
|
)
|
|
}
|
|
|
|
function SidebarMenuClientOnly() {
|
|
const [mounted, setMounted] = useState(false);
|
|
useEffect(() => setMounted(true), []);
|
|
if (!mounted) return null;
|
|
return <SidebarMenu />;
|
|
}
|
|
|
|
function SidebarMenu() {
|
|
const [open, setOpen] = useState(false)
|
|
const { isMobile } = useDevice()
|
|
|
|
if (!isMobile) return null
|
|
|
|
return (
|
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
<SheetTrigger asChild>
|
|
<button
|
|
aria-label="打开菜单"
|
|
className="p-2 rounded-md hover:bg-accent transition-colors"
|
|
>
|
|
<Menu className="w-6 h-6" />
|
|
</button>
|
|
</SheetTrigger>
|
|
<SheetContent side="right" className="p-0 w-64">
|
|
{/* 可访问性要求的标题,视觉上隐藏 */}
|
|
<SheetTitle className="sr-only">侧边栏菜单</SheetTitle>
|
|
<nav className="flex flex-col gap-2 p-4">
|
|
{navbarMenuComponents.map((item) =>
|
|
item.href ? (
|
|
<Link
|
|
key={item.title}
|
|
href={item.href}
|
|
className="py-2 px-3 rounded hover:bg-accent font-bold transition-colors"
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
{item.title}
|
|
</Link>
|
|
) : item.children ? (
|
|
<div key={item.title} className="mb-2">
|
|
<div className="font-bold px-3 py-2">{item.title}</div>
|
|
<div className="flex flex-col pl-4">
|
|
{item.children.map((child) => (
|
|
<Link
|
|
key={child.title}
|
|
href={child.href}
|
|
className="py-2 px-3 rounded hover:bg-accent transition-colors"
|
|
onClick={() => setOpen(false)}
|
|
>
|
|
{child.title}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
) : null
|
|
)}
|
|
</nav>
|
|
</SheetContent>
|
|
</Sheet>
|
|
)
|
|
} |