snapshot before merge
This commit is contained in:
1 parent
2b3b2850f9
commit
21fc65b3ae
145 files changed
+9786
-59
No files matched your search
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"slug": "2026-06-01-测试",
|
||||
"title": "测试",
|
||||
"deletedAt": "2026-06-01T06:05:06.023Z",
|
||||
"originalPath": "E:\\GitHub\\blog\\content\\post\\2026\\2026-06-01-测试",
|
||||
"expiresAt": "2026-07-01T06:05:06.023Z"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
title: 测试
|
||||
date: '2026-06-01'
|
||||
slug: 2026-06-01-测试
|
||||
draft: false
|
||||
categories: []
|
||||
tags: []
|
||||
author: qunlin
|
||||
layout: post
|
||||
---
|
||||
测试
|
||||
|
||||

|
||||
@@ -389,8 +389,8 @@ export default function CommentsPage() {
|
||||
// --- Login ---
|
||||
if (!loggedIn) {
|
||||
return (
|
||||
<div className="min-h-full flex items-center justify-center px-8">
|
||||
<div className="w-[340px] bg-card border border-line rounded-lg p-8">
|
||||
<div className="min-h-full flex items-center justify-center px-4 md:px-8">
|
||||
<div className="w-full max-w-[340px] mx-auto bg-card border border-line rounded-lg p-6 md:p-8">
|
||||
<div className="text-center mb-6">
|
||||
<div className="w-10 h-10 bg-accent/8 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<Key size={18} className="text-accent" />
|
||||
@@ -430,9 +430,9 @@ export default function CommentsPage() {
|
||||
|
||||
// --- Main ---
|
||||
return (
|
||||
<div className="h-full flex flex-col px-8 py-6">
|
||||
<div className="h-full flex flex-col px-4 py-4 md:px-8 md:py-6">
|
||||
{/* Top bar */}
|
||||
<div className="flex items-center justify-between mb-5 shrink-0">
|
||||
<div className="flex items-center justify-between mb-5 shrink-0 gap-3 flex-wrap">
|
||||
<h2 className="text-lg font-bold text-text">评论管理</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<button type="button" onClick={() => setViewMode("comments")}
|
||||
@@ -474,7 +474,7 @@ export default function CommentsPage() {
|
||||
) : stats ? (
|
||||
<div className="space-y-6">
|
||||
{/* Summary cards */}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
{[
|
||||
{ label: "文章总数", value: stats.totalArticles },
|
||||
{ label: "评论总数", value: stats.totalComments },
|
||||
@@ -488,7 +488,7 @@ export default function CommentsPage() {
|
||||
</div>
|
||||
|
||||
{/* Top articles & top users side by side */}
|
||||
<div className="flex gap-5 min-h-0">
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-5 min-h-0">
|
||||
{/* Top articles */}
|
||||
<div className="flex-1 bg-card border border-line rounded-lg p-5">
|
||||
<h3 className="text-[13px] font-bold text-text mb-4">评论最多的文章 Top 20</h3>
|
||||
@@ -528,9 +528,9 @@ export default function CommentsPage() {
|
||||
|
||||
{/* Two columns — only in comments mode */}
|
||||
{viewMode === "comments" && (
|
||||
<div className="flex gap-5 flex-1 min-h-0">
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-5 flex-1 min-h-0">
|
||||
{/* Left: Articles */}
|
||||
<div className="w-[240px] shrink-0 flex flex-col min-h-0">
|
||||
<div className="w-full sm:w-[240px] shrink-0 flex flex-col min-h-0 sm:max-h-full">
|
||||
<div className="text-[11px] font-medium text-muted uppercase tracking-wider mb-2.5 shrink-0">
|
||||
文章列表 {pagesTotal > 0 && <span className="font-normal normal-case">· {pagesTotal}</span>}
|
||||
</div>
|
||||
@@ -573,7 +573,7 @@ export default function CommentsPage() {
|
||||
<>
|
||||
<div className="flex items-center gap-2 mb-2.5 shrink-0">
|
||||
{editingTitle ? (
|
||||
<div className="flex items-center gap-1.5 flex-1">
|
||||
<div className="flex items-center gap-1.5 flex-1 min-w-0">
|
||||
<input
|
||||
type="text"
|
||||
value={editTitleText}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function EditPostPage({
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors" title="返回">
|
||||
<ArrowLeft size={16} />
|
||||
@@ -49,7 +49,7 @@ export default function EditPostPage({
|
||||
|
||||
if (error || !post) {
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors" title="返回">
|
||||
<ArrowLeft size={16} />
|
||||
@@ -62,7 +62,7 @@ export default function EditPostPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-3">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors" title="返回">
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function NewPostPage() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center gap-3 mb-8">
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -120,8 +120,8 @@ export default function FeedsPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center justify-between mb-6 sm:mb-8 gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-3">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors">
|
||||
<ArrowLeft size={16} />
|
||||
@@ -153,7 +153,7 @@ export default function FeedsPage() {
|
||||
|
||||
{showAdd && (
|
||||
<div className="mb-6 p-5 bg-card border border-line rounded-lg">
|
||||
<form onSubmit={handleAdd} className="flex gap-3 items-end">
|
||||
<form onSubmit={handleAdd} className="flex flex-col sm:flex-row gap-3 sm:items-end">
|
||||
<div className="flex-1">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">博客名称</label>
|
||||
<input type="text" value={addForm.feedTitle} onChange={(e) => setAddForm({ ...addForm, feedTitle: e.target.value })}
|
||||
@@ -193,7 +193,7 @@ export default function FeedsPage() {
|
||||
: null}
|
||||
|
||||
{editTarget === f.url ? (
|
||||
<div className="flex-1 flex gap-2 items-end">
|
||||
<div className="flex-1 flex flex-col sm:flex-row gap-2 sm:items-end">
|
||||
<div className="flex-1">
|
||||
<input type="text" value={editForm.feedTitle} onChange={(e) => setEditForm({ ...editForm, feedTitle: e.target.value })}
|
||||
className="w-full bg-bg border border-line rounded-md px-2 py-1.5 text-[13px] focus:outline-none focus:border-accent/30" title="名称" placeholder="博客名称" />
|
||||
@@ -215,7 +215,7 @@ export default function FeedsPage() {
|
||||
)}
|
||||
|
||||
{editTarget !== f.url && (
|
||||
<div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-all">
|
||||
<div className="flex items-center gap-0.5 shrink-0 md:opacity-0 md:group-hover:opacity-100 transition-all">
|
||||
<button type="button" onClick={() => startEdit(f)} className="p-1.5 rounded hover:bg-surface text-muted hover:text-text transition-colors" title="编辑">
|
||||
<Edit size={13} />
|
||||
</button>
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function ImagesPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center gap-3">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors">
|
||||
@@ -84,7 +84,7 @@ export default function ImagesPage() {
|
||||
<p className="text-sm text-muted">暂无图片</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-4 gap-3">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3">
|
||||
{images.map((img) => (
|
||||
<div key={img.path} className="bg-card border border-line rounded-lg overflow-hidden group hover:border-accent/20 transition-all">
|
||||
<div className="aspect-square bg-surface flex items-center justify-center overflow-hidden">
|
||||
|
||||
@@ -25,9 +25,9 @@ export default function RootLayout({
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
</head>
|
||||
<body className="h-full flex bg-bg text-text">
|
||||
<body className="h-full md:flex bg-bg text-text">
|
||||
<Sidebar />
|
||||
<main className="flex-1 overflow-auto">
|
||||
<main className="flex-1 overflow-auto pt-12 md:pt-0 h-full">
|
||||
{children}
|
||||
</main>
|
||||
</body>
|
||||
|
||||
@@ -151,8 +151,8 @@ export default function LinksPage() {
|
||||
const hiddenCount = links.filter((l) => l.hidden).length;
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="flex items-center justify-between mb-6 sm:mb-8 gap-3 flex-wrap">
|
||||
<div className="flex items-center gap-3">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors">
|
||||
<ArrowLeft size={16} />
|
||||
@@ -192,7 +192,7 @@ export default function LinksPage() {
|
||||
{/* Add form */}
|
||||
{showAdd && (
|
||||
<div className="mb-6 p-5 bg-card border border-line rounded-lg">
|
||||
<form onSubmit={handleAdd} className="flex gap-3 items-end">
|
||||
<form onSubmit={handleAdd} className="flex flex-col sm:flex-row gap-3 sm:items-end">
|
||||
<div className="flex-1">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">网站名称</label>
|
||||
<input type="text" value={addForm.name} onChange={(e) => setAddForm({ ...addForm, name: e.target.value })}
|
||||
@@ -243,7 +243,7 @@ export default function LinksPage() {
|
||||
|
||||
{/* Info or Edit form */}
|
||||
{editTarget === l.url ? (
|
||||
<div className="flex-1 flex gap-2 items-end">
|
||||
<div className="flex-1 flex flex-col sm:flex-row gap-2 sm:items-end">
|
||||
<div className="flex-1">
|
||||
<input type="text" value={editForm.name} onChange={(e) => setEditForm({ ...editForm, name: e.target.value })}
|
||||
className="w-full bg-bg border border-line rounded-md px-2 py-1.5 text-[13px] focus:outline-none focus:border-accent/30" title="名称" placeholder="名称" />
|
||||
|
||||
+30
-11
@@ -121,10 +121,10 @@ export default function HomePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
{/* Dashboard stats */}
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
{/* Dashboard stats: hidden on mobile, full cards on >=sm */}
|
||||
{stats && (
|
||||
<div className="grid grid-cols-3 gap-4 mb-8">
|
||||
<div className="hidden sm:grid sm:grid-cols-3 gap-4 mb-8">
|
||||
<div className="bg-card border border-line rounded-lg p-4 flex items-center gap-3">
|
||||
<div className="w-9 h-9 bg-accent/8 rounded-lg flex items-center justify-center">
|
||||
<PostIcon size={17} className="text-accent" />
|
||||
@@ -156,7 +156,7 @@ export default function HomePage() {
|
||||
)}
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="hidden md:flex items-center justify-between mb-6 gap-3 flex-wrap">
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-1">
|
||||
<button type="button" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors">
|
||||
@@ -186,7 +186,7 @@ export default function HomePage() {
|
||||
|
||||
{/* Batch action bar */}
|
||||
{selectMode && selected.size > 0 && (
|
||||
<div className="mb-4 p-3 bg-accent/5 border border-accent/20 rounded-lg flex items-center gap-3">
|
||||
<div className="mb-4 p-3 bg-accent/5 border border-accent/20 rounded-lg flex items-center gap-3 flex-wrap">
|
||||
<span className="text-[12px] font-medium text-text">已选 {selected.size} 篇</span>
|
||||
<select
|
||||
value={batchAction || ""}
|
||||
@@ -220,11 +220,30 @@ export default function HomePage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-6">
|
||||
<Search size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted/50" />
|
||||
<input type="text" value={search} onChange={(e) => { setSearch(e.target.value); setPage(1); }}
|
||||
placeholder="搜索标题、分类..." className="w-full pl-9 pr-4 py-2.5 bg-card border border-line rounded-md text-[13px] text-text placeholder:text-muted/40 focus:outline-none focus:border-accent/30 transition-colors" />
|
||||
{/* Search + (mobile) quick actions */}
|
||||
<div className="flex items-center gap-2 mb-6">
|
||||
<div className="relative flex-1">
|
||||
<Search size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-muted/50" />
|
||||
<input type="text" value={search} onChange={(e) => { setSearch(e.target.value); setPage(1); }}
|
||||
placeholder="搜索标题、分类..." className="w-full pl-9 pr-4 py-2.5 bg-card border border-line rounded-md text-[13px] text-text placeholder:text-muted/40 focus:outline-none focus:border-accent/30 transition-colors" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setSelectMode(!selectMode); setSelected(new Set()); setBatchAction(null); }}
|
||||
aria-label="批量"
|
||||
className={`md:hidden shrink-0 h-[38px] w-[38px] inline-flex items-center justify-center rounded-md border transition-colors ${
|
||||
selectMode ? "bg-accent/5 border-accent/30 text-accent" : "border-line text-muted hover:text-accent"
|
||||
}`}
|
||||
>
|
||||
<CheckSquare size={16} />
|
||||
</button>
|
||||
<Link
|
||||
href="/edit"
|
||||
aria-label="新建"
|
||||
className="md:hidden shrink-0 h-[38px] w-[38px] inline-flex items-center justify-center rounded-md bg-accent text-white hover:bg-accent-hover transition-colors"
|
||||
>
|
||||
<Plus size={17} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Article list */}
|
||||
@@ -276,7 +295,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
|
||||
{!selectMode && (
|
||||
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div className="flex items-center gap-0.5 md:opacity-0 md:group-hover:opacity-100 transition-opacity">
|
||||
<button type="button" onClick={() => handlePreview(post.slug)}
|
||||
className="p-1.5 rounded hover:bg-accent/8 text-muted hover:text-accent transition-colors" title="预览"><Eye size={14} /></button>
|
||||
<button type="button" onClick={() => router.push(`/edit/${post.slug}`)}
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function RecyclePage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="px-8 py-8">
|
||||
<div className="px-4 py-6 md:px-8 md:py-8">
|
||||
<div className="mb-8">
|
||||
<h2 className="text-lg font-bold text-text">回收站</h2>
|
||||
<p className="text-[13px] text-muted mt-0.5">删除的文章保留 30 天,到期自动清理</p>
|
||||
|
||||
@@ -95,7 +95,7 @@ export default function WechatPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col px-8 py-6">
|
||||
<div className="h-full flex flex-col px-4 py-4 md:px-8 md:py-6">
|
||||
<div className="flex items-center justify-between mb-6 shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<button type="button" aria-label="返回上一页" onClick={() => router.back()} className="p-1 -ml-1 rounded hover:bg-surface text-muted hover:text-text transition-colors">
|
||||
@@ -119,9 +119,9 @@ export default function WechatPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-5 flex-1 min-h-0">
|
||||
<div className="flex flex-col sm:flex-row gap-4 sm:gap-5 flex-1 min-h-0">
|
||||
{/* Left: article list */}
|
||||
<div className="w-[300px] shrink-0 flex flex-col min-h-0">
|
||||
<div className="w-full sm:w-[260px] sm:max-w-[300px] shrink-0 flex flex-col min-h-0 sm:max-h-full">
|
||||
<input
|
||||
type="text"
|
||||
value={search}
|
||||
@@ -153,16 +153,16 @@ export default function WechatPage() {
|
||||
<div className="flex-1 flex flex-col min-h-0 min-w-0">
|
||||
{selected ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-3 shrink-0">
|
||||
<div className="text-[13px] font-medium text-text truncate">{selected.title}</div>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-3 shrink-0">
|
||||
<div className="text-[13px] font-medium text-text truncate min-w-0">{selected.title}</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSync}
|
||||
disabled={syncing}
|
||||
className="flex items-center gap-1.5 px-4 py-2 text-[13px] font-medium rounded-md bg-accent text-white hover:bg-accent-hover disabled:opacity-50 transition-colors"
|
||||
className="shrink-0 w-full sm:w-auto inline-flex items-center justify-center gap-1.5 px-4 py-2 text-[13px] font-medium rounded-md bg-accent text-white hover:bg-accent-hover disabled:opacity-50 transition-colors whitespace-nowrap"
|
||||
>
|
||||
{syncing ? <Loader2 size={13} className="animate-spin" /> : <Send size={13} />}
|
||||
{syncing ? "同步中..." : "同步到微信草稿"}
|
||||
<span>{syncing ? "同步中..." : "同步到微信草稿"}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function Pagination({ page, totalPages, onPageChange }: Paginatio
|
||||
const btn = "px-2.5 py-1.5 text-[12px] rounded-md border border-line hover:bg-surface transition-colors";
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center gap-1 mt-8">
|
||||
<div className="flex items-center justify-center gap-1 mt-8 flex-wrap">
|
||||
<button type="button" onClick={() => onPageChange(page - 1)} disabled={page <= 1}
|
||||
className={`${btn} ${page <= 1 ? "opacity-25 cursor-not-allowed" : ""}`}>上一页</button>
|
||||
{start > 1 && (
|
||||
|
||||
@@ -265,7 +265,7 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col lg:flex-row lg:gap-8 gap-6">
|
||||
<div className="flex-1 min-w-0 space-y-8">
|
||||
{message && (
|
||||
<div className={`px-4 py-2.5 rounded-md text-[13px] font-medium ${
|
||||
@@ -281,7 +281,7 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
<section>
|
||||
<h3 className="text-[11px] font-medium text-muted uppercase tracking-wider mb-4">文章信息</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">标题</label>
|
||||
<input
|
||||
@@ -304,7 +304,7 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
placeholder="文章标题"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[200px]">
|
||||
<div className="w-full sm:w-[200px]">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">
|
||||
Slug <span className="text-[10px] text-muted/50 font-normal">(自动时间戳)</span>
|
||||
</label>
|
||||
@@ -312,12 +312,12 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="w-[180px]">
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="w-full sm:w-[180px]">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">日期</label>
|
||||
<input type="date" value={form.date} onChange={(e) => setForm({ ...form, date: e.target.value })} className={inputCls} title="日期" />
|
||||
</div>
|
||||
<div className="w-[160px]">
|
||||
<div className="w-full sm:w-[160px]">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">作者</label>
|
||||
<input type="text" value={form.author} onChange={(e) => setForm({ ...form, author: e.target.value })} className={inputCls} title="作者" />
|
||||
</div>
|
||||
@@ -327,7 +327,7 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="flex-1">
|
||||
<label className="block text-[12px] font-medium text-text/70 mb-1.5">标签</label>
|
||||
<input type="text" value={form.tags} onChange={(e) => setForm({ ...form, tags: e.target.value })} className={inputCls} placeholder="用顿号分隔" />
|
||||
@@ -377,7 +377,7 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
</section>
|
||||
|
||||
{/* Actions: 草稿 | 保存 | 发布 */}
|
||||
<div className="flex items-center gap-3 pt-2">
|
||||
<div className="flex items-center gap-3 pt-2 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDraft}
|
||||
@@ -411,8 +411,8 @@ export default function PostEditor({ initialData, isEditing }: PostEditorProps)
|
||||
</div>
|
||||
|
||||
{/* Right: shortcuts + AI panel */}
|
||||
<div className="w-[180px] shrink-0">
|
||||
<div className="sticky top-6 space-y-6">
|
||||
<div className="w-full lg:w-[180px] lg:shrink-0">
|
||||
<div className="lg:sticky lg:top-6 space-y-6">
|
||||
<ShortcutPanel textareaRef={textareaRef} value={content} onChange={setContent} />
|
||||
<AIPanel
|
||||
selectedText={selectedText}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { FileText, PenSquare, MessageSquare, Trash2, Rss, Link2, MessageCircle, Image } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FileText, PenSquare, MessageSquare, Trash2, Rss, Link2, MessageCircle, Image, Menu, X } from "lucide-react";
|
||||
import ThemeToggle from "./ThemeToggle";
|
||||
|
||||
const navItems = [
|
||||
@@ -18,6 +19,16 @@ const navItems = [
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
useEffect(() => { setOpen(false); }, [pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const original = document.body.style.overflow;
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => { document.body.style.overflow = original; };
|
||||
}, [open]);
|
||||
|
||||
const isActive = (href: string) => {
|
||||
if (href === "/") return pathname === "/";
|
||||
@@ -25,13 +36,28 @@ export default function Sidebar() {
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="w-1/4 min-w-[200px] max-w-[260px] bg-surface border-r border-line flex flex-col shrink-0">
|
||||
<>
|
||||
<header className="md:hidden fixed top-0 inset-x-0 z-40 h-12 bg-surface border-b border-line flex items-center px-3">
|
||||
<button type="button" onClick={() => setOpen(true)} className="p-2 -ml-1 rounded-md text-text/80 hover:bg-bg hover:text-accent transition-colors" aria-label="menu">
|
||||
<Menu size={18} />
|
||||
</button>
|
||||
<span className="ml-2 text-[13px] font-bold text-accent tracking-wider">{"\u5199\u4f5c\u540e\u53f0"}</span>
|
||||
</header>
|
||||
{open && (
|
||||
<div className="md:hidden fixed inset-0 z-40 bg-[#223d66]/30 backdrop-blur-sm" onClick={() => setOpen(false)} aria-hidden />
|
||||
)}
|
||||
<aside className={`bg-surface border-r border-line flex flex-col shrink-0 transition-transform duration-200 ease-out md:w-1/4 md:min-w-[200px] md:max-w-[260px] md:static md:translate-x-0 fixed top-0 left-0 z-50 h-full w-[78%] max-w-[280px] ${open ? "translate-x-0" : "-translate-x-full md:translate-x-0"}`}>
|
||||
{/* Logo */}
|
||||
<div className="px-5 py-5 border-b border-line">
|
||||
<div className="px-5 py-5 border-b border-line flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<h1 className="text-[15px] font-bold text-accent tracking-wider">
|
||||
写作后台
|
||||
</h1>
|
||||
<p className="text-[11px] text-muted/70 mt-0.5">优世界</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setOpen(false)} className="md:hidden p-1.5 -mr-1 rounded-md text-muted hover:text-text hover:bg-bg transition-colors" aria-label="close">
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Nav */}
|
||||
@@ -61,5 +87,6 @@ export default function Sidebar() {
|
||||
<p className="text-[10px] text-muted/50">localhost:8016</p>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user