diff --git a/CHANGELOG.md b/CHANGELOG.md index cf74d08..ebe1c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,6 @@ ## [2.4.0](https://github.com/one-ea/Monolith/compare/v2.3.2...v2.4.0) (2026-05-03) -### ✨ Features +### Features * **ui:** improve blog navigation and social links ([0851c95](https://github.com/one-ea/Monolith/commit/0851c95db1e2fb9a8c0bdf193326a63fb16af2cf)) diff --git a/client/src/components/admin-layout.tsx b/client/src/components/admin-layout.tsx index 570b85a..b1aea0e 100644 --- a/client/src/components/admin-layout.tsx +++ b/client/src/components/admin-layout.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Link, useLocation } from "wouter"; import { clearToken } from "@/lib/api"; import { @@ -14,6 +14,7 @@ import { ExternalLink, Menu, Search, + X, } from "lucide-react"; import { ThemeToggle } from "@/components/theme-toggle"; @@ -21,9 +22,36 @@ interface AdminLayoutProps { children: React.ReactNode; } +const NAV_GROUPS = [ + { + title: "内容管理", + items: [ + { href: "/admin", icon: LayoutDashboard, label: "控制台" }, + { href: "/admin/pages", icon: StickyNote, label: "独立页面" }, + { href: "/admin/comments", icon: MessageCircle, label: "评论审核" }, + ], + }, + { + title: "资源与数据", + items: [ + { href: "/admin/media", icon: ImageIcon, label: "媒体库" }, + { href: "/admin/analytics", icon: BarChart3, label: "数据分析" }, + { href: "/admin/seo", icon: Sparkles, label: "SEO 优化" }, + { href: "/admin/backup", icon: HardDrive, label: "安全备份" }, + ], + }, + { + title: "系统配置", + items: [ + { href: "/admin/settings", icon: Settings, label: "站点设置" }, + ], + }, +]; + export function AdminLayout({ children }: AdminLayoutProps) { const [location, setLocation] = useLocation(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); + const [navQuery, setNavQuery] = useState(""); useEffect(() => { if (!mobileMenuOpen) return; @@ -39,52 +67,33 @@ export function AdminLayout({ children }: AdminLayoutProps) { setLocation("/admin/login"); }; - const navGroups = [ - { - title: "内容管理", - items: [ - { href: "/admin", icon: LayoutDashboard, label: "控制台" }, - { href: "/admin/pages", icon: StickyNote, label: "独立页面" }, - { href: "/admin/comments", icon: MessageCircle, label: "评论审核" }, - ], - }, - { - title: "资源与数据", - items: [ - { href: "/admin/media", icon: ImageIcon, label: "媒体库" }, - { href: "/admin/analytics", icon: BarChart3, label: "数据分析" }, - { href: "/admin/seo", icon: Sparkles, label: "SEO 优化" }, - { href: "/admin/backup", icon: HardDrive, label: "安全备份" }, - ], - }, - { - title: "系统配置", - items: [ - { href: "/admin/settings", icon: Settings, label: "站点设置" }, - ], - }, - ]; - const currentTitle = - navGroups.flatMap((group) => group.items).find((item) => + NAV_GROUPS.flatMap((group) => group.items).find((item) => item.href === "/admin" ? location === "/admin" : location.startsWith(item.href) )?.label || "管理后台"; + const filteredNavGroups = useMemo(() => { + const query = navQuery.trim().toLowerCase(); + if (!query) return NAV_GROUPS; + + return NAV_GROUPS + .map((group) => ({ + ...group, + items: group.items.filter((item) => + item.label.toLowerCase().includes(query) + || item.href.toLowerCase().includes(query) + || group.title.toLowerCase().includes(query) + ), + })) + .filter((group) => group.items.length > 0); + }, [navQuery]); + const SidebarFooter = () => ( -
+
- 主题 + 主题
- - - 查看站点 - + )} +
))} + {filteredNavGroups.length === 0 && ( +
+ 没有匹配的模块 +
+ )} @@ -152,7 +185,7 @@ export function AdminLayout({ children }: AdminLayoutProps) { return (
-
-
-
-

Monolith 管理后台

-

{currentTitle}

+
+
+ Monolith 管理后台 + / + {currentTitle}
-
-
+
+
{/* 封面区 */} -
-
+
+
{cover ? ( ) : ( -
- +
+ {getInitial(post.title)}
@@ -49,7 +49,7 @@ export function ArticleCard({ post }: { post: PostMeta }) { {/* 内容区 */}
-
+
{post.pinned && ( @@ -59,17 +59,26 @@ export function ArticleCard({ post }: { post: PostMeta }) { {post.tags.slice(0, 2).map((tag) => ( {tag} ))} - {formatDate(post.createdAt)} + + + {formatDate(post.createdAt)} +
-

+

{post.title}

-

+

{post.excerpt}

-
- 阅读全文 - +
+ + + {post.category || "未分类"} + + + 阅读全文 + +
diff --git a/client/src/components/cookie-consent.tsx b/client/src/components/cookie-consent.tsx index 01c9196..89131e9 100644 --- a/client/src/components/cookie-consent.tsx +++ b/client/src/components/cookie-consent.tsx @@ -41,8 +41,14 @@ export function CookieConsent() { return (
-
- 本站使用 Cookie 进行访问统计与第三方脚本加载。继续访问即表示您同意我们的{" "} +
+ + 本站使用 Cookie 进行访问统计与第三方脚本加载。继续访问即表示您同意我们的{" "} + + + 本站使用 Cookie 进行访问统计。 + 继续访问即表示您同意 + 隐私政策
diff --git a/client/src/components/hero.tsx b/client/src/components/hero.tsx index 611e208..65ef6cd 100644 --- a/client/src/components/hero.tsx +++ b/client/src/components/hero.tsx @@ -1,23 +1,42 @@ export function Hero() { return ( -
-
-
+
+
+
+
+
+
+
+
+
+ EDGE / DESIGN / CODE +
+ +

+ Monolith +

+

+ 书写代码、设计系统与边缘计算的个人技术档案。以更清晰的网格组织阅读路径,让文章、标签和长期主题更容易被发现。 +

+

+ 书写代码、设计系统与边缘计算。 + 以清晰网格组织阅读路径。 + 让文章、标签和长期主题更容易被发现。 +

+
+ +
+

CURRENT FOCUS

+
+ {["工程笔记", "设计观察", "边缘计算"].map((item) => ( +
+ {item} + +
+ ))} +
+
-
-
-
-
-

- Monolith -

-

- 书写代码、设计与边缘计算的个人博客。 -
- - 在秩序与混沌的交界处,寻找属于自己的巨石碑。 - -

); } diff --git a/client/src/components/navbar.tsx b/client/src/components/navbar.tsx index b048a1d..3f9eee9 100644 --- a/client/src/components/navbar.tsx +++ b/client/src/components/navbar.tsx @@ -63,7 +63,7 @@ export function Navbar() { onDoubleClick={handleLogoDoubleClick} >
-
+
Monolith diff --git a/client/src/components/reading-controls.tsx b/client/src/components/reading-controls.tsx index e73a04c..5630e47 100644 --- a/client/src/components/reading-controls.tsx +++ b/client/src/components/reading-controls.tsx @@ -105,7 +105,7 @@ export function ReadingControls({
@@ -132,13 +132,13 @@ export function ReadingControls({
@@ -193,7 +193,7 @@ export function ReadingControls({
))}
浏览量 - +
-

{viewStats?.totalViews?.toLocaleString() ?? "—"}

+

{viewStats?.totalViews?.toLocaleString() ?? "—"}

@@ -173,7 +227,7 @@ export function AdminDashboard() { setSearch(e.target.value)} placeholder="搜索标题、Slug 或标签..." - className="h-[44px] w-full rounded-md border border-border/20 bg-background/35 pl-[36px] pr-[14px] text-[14px] text-foreground outline-none transition-all placeholder:text-muted-foreground/35 focus:border-foreground/25 focus:bg-background/55" + className="h-[36px] w-full rounded-md border border-border/20 bg-background/35 pl-[36px] pr-[14px] text-[13px] text-foreground outline-none transition-all placeholder:text-muted-foreground/35 focus:border-foreground/25 focus:bg-background/55" />
@@ -184,32 +238,32 @@ export function AdminDashboard() { {/* ─── 文章列表 ─── */}
-

+

{filter === "all" ? "所有文章" : filter === "published" ? "已发布" : "草稿箱"} - {selectedTag && <>·{selectedTag}} + {selectedTag && <>·{selectedTag}}

{filteredPosts.length} 篇
{/* 批量操作工具栏 */} {filteredPosts.length > 0 && ( -
0 ? "border-cyan-500/30 bg-cyan-500/5" : ""}`}> +
0 ? "border-foreground/20 bg-foreground/[0.03]" : ""}`}>
-
{selectedSlugs.size > 0 && (
- - -
@@ -226,40 +280,46 @@ export function AdminDashboard() { {search || selectedTag ? "没有符合条件的文章" : "暂无文章"}

{!search && !selectedTag && ( - + 写第一篇 )}
) : (
-
+
+
+ + 文章 + 浏览 + 操作 +
{filteredPosts.map((post) => ( -
+
{/* 复选框 */} - {/* 状态指示点 */} -
+
-
- {post.title} +
+ {post.title} {post.pinned && 置顶}
{timeAgo(post.updatedAt || post.createdAt)} - {(post.viewCount ?? 0).toLocaleString()} + {(post.viewCount ?? 0).toLocaleString()} {post.tags.length > 0 && {post.tags.slice(0, 2).join(" · ")}}
{/* 操作按钮 — hover 显现 */}
- + @@ -276,79 +336,16 @@ export function AdminDashboard() { )}
- {/* ─── 右侧边栏:标签 + 热门 + SEO ─── */} + {/* ─── 右侧边栏:标签 + 热门 ─── */}
- {/* SEO 健康状态 */} - {posts.length > 0 && (() => { - const published = posts.filter(p => p.published); - const withExcerpt = published.filter(p => p.excerpt && p.excerpt.trim().length > 0); - const withTags = published.filter(p => p.tags.length > 0); - const goodSlug = published.filter(p => /^[a-z0-9-]+$/.test(p.slug) && !p.slug.includes("--") && !p.slug.startsWith("-") && !p.slug.endsWith("-")); - const withTitle50 = published.filter(p => p.title.length <= 60 && p.title.length >= 5); - - const checks = [ - { label: "Meta 摘要", ok: withExcerpt.length, total: published.length, desc: "已填写 excerpt" }, - { label: "标签覆盖", ok: withTags.length, total: published.length, desc: "至少 1 个标签" }, - { label: "URL 规范", ok: goodSlug.length, total: published.length, desc: "slug 为小写+连字符" }, - { label: "标题长度", ok: withTitle50.length, total: published.length, desc: "5-60 字符" }, - ]; - - const totalOk = checks.reduce((s, c) => s + c.ok, 0); - const totalAll = checks.reduce((s, c) => s + c.total, 0); - const score = totalAll > 0 ? Math.round((totalOk / totalAll) * 100) : 0; - - const scoreColor = score >= 90 ? "text-emerald-400" : score >= 70 ? "text-amber-400" : "text-red-400"; - const scoreBg = score >= 90 ? "bg-emerald-500/8" : score >= 70 ? "bg-amber-500/8" : "bg-red-500/8"; - const scoreBorder = score >= 90 ? "border-emerald-500/20" : score >= 70 ? "border-amber-500/20" : "border-red-500/20"; - - return ( -
-
-

- SEO 健康 -

- {score}% -
-
- {checks.map(c => { - const pct = c.total > 0 ? Math.round((c.ok / c.total) * 100) : 0; - const Icon = pct === 100 ? CheckCircle2 : pct >= 70 ? AlertTriangle : XCircle; - const color = pct === 100 ? "text-emerald-400/70" : pct >= 70 ? "text-amber-400/70" : "text-red-400/60"; - return ( -
- - {c.label} - {c.ok}/{c.total} -
- ); - })} -
- {/* sitemap + robots 固定指标 */} -
- {[ - { label: "Sitemap", ok: true }, - { label: "Robots noindex (404)", ok: true }, - { label: "JSON-LD 结构化", ok: true }, - { label: "OG 社交标签", ok: true }, - ].map(item => ( -
- - {item.label} -
- ))} -
-
- ); - })()} - {/* 标签 */} {allTags.length > 0 && (

标签

{allTags.length > 8 && ( - )} @@ -358,10 +355,10 @@ export function AdminDashboard() { const count = posts.filter((p) => p.tags.includes(tag)).length; return ( - {wordCount} 字 +
+

{isEdit ? "EDITING" : "NEW DRAFT"}

+

+ {form.title || "未命名文章"} +

+
+ {wordCount} 字 {lastSaved && ( <> - | - 上次保存 {lastSaved} + / + 上次保存 {lastSaved} )}
-
+
{message.text && ( - {message.type === "success" ? "✓" : "✕"} {message.text} )} - -