修复笔记侧边栏多个 bug

- 移除管理员面板入口(UserMenu 中的按钮和相关 import)
- 修复重复文件夹问题:seed 判断同时检查 notes 和 folders 数量
- 文件夹新增 hover 删除按钮,附带确认弹窗
- 文件夹区域添加 overflow-y-auto 和 maxHeight 限制,防止过多文件夹遮挡笔记列表

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MikiVL 2026-07-26 09:02:27 +08:00
parent 689c8b2d86
commit b02f23729b
3 changed files with 61 additions and 7 deletions

View File

@ -76,6 +76,7 @@ export function UserMenu() {
<span></span>
</button>
)}
</div>
{showProfile && <ProfileModal onClose={() => setShowProfile(false)} />}

View File

@ -48,6 +48,7 @@ export function Sidebar() {
} | null>(null)
const [draggingNoteId, setDraggingNoteId] = useState<string | null>(null)
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null)
const [deleteFolderConfirm, setDeleteFolderConfirm] = useState<string | null>(null)
const sensors = useSensors(
useSensor(MouseSensor, { activationConstraint: { distance: 6 } }),
@ -178,6 +179,7 @@ export function Sidebar() {
if (e.key === 'Enter') handleRenameFolder(folder.id)
if (e.key === 'Escape') setEditingFolderId(null)
}}
onDelete={() => setDeleteFolderConfirm(folder.id)}
/>
{expandedFolders.has(folder.id) && renderFolder(folder.id, depth + 1)}
</div>
@ -211,7 +213,7 @@ export function Sidebar() {
</div>
{/* Search */}
<div className="px-3 py-2">
<div className="px-3 py-2 shrink-0">
<div
className="flex items-center gap-2 rounded-lg px-3 py-1.5"
style={{ background: 'var(--bg-muted)', border: '1px solid var(--border)' }}
@ -228,7 +230,7 @@ export function Sidebar() {
</div>
{/* Nav shortcuts */}
<nav className="px-2 space-y-0.5">
<nav className="px-2 space-y-0.5 shrink-0">
<NavItem icon={<BookOpen size={14} />} label="使用指南" active={activeNoteId === '__welcome__'} onClick={() => setActiveNote('__welcome__')} />
<NavItem icon={<FileText size={14} />} label="所有笔记" count={notes.length} active={activeFolderId === 'all' && activeNoteId !== '__welcome__'} onClick={() => { setActiveFolder('all'); if (activeNoteId === '__welcome__') setActiveNote(null) }} />
<NavItem icon={<Star size={14} />} label="收藏" count={notes.filter(n => n.starred).length} active={activeFolderId === 'starred'} onClick={() => { setActiveFolder('starred'); if (activeNoteId === '__welcome__') setActiveNote(null) }} />
@ -242,14 +244,14 @@ export function Sidebar() {
</nav>
{/* Folders section */}
<div className="px-3 pt-3 pb-1 flex items-center justify-between">
<div className="px-3 pt-3 pb-1 flex items-center justify-between shrink-0">
<span className="text-xs font-semibold uppercase tracking-wider" style={{ color: 'var(--text-faint)' }}></span>
<button onClick={() => setCreatingFolder(true)} className="toolbar-btn" title="新建文件夹">
<FolderPlus size={13} />
</button>
</div>
<div className="px-2 space-y-0.5">
<div className="px-2 space-y-0.5 overflow-y-auto" style={{ maxHeight: '30%' }}>
{renderFolder(null, 0)}
{creatingFolder && (
@ -472,6 +474,36 @@ export function Sidebar() {
</div>
</div>
)}
{deleteFolderConfirm && (
<div
className="fixed inset-0 z-50 flex items-center justify-center"
style={{ background: 'rgba(0,0,0,0.4)' }}
onClick={() => setDeleteFolderConfirm(null)}
>
<div
className="rounded-2xl shadow-2xl p-5 flex flex-col gap-4"
style={{ background: 'var(--bg)', border: '1px solid var(--border)', width: 320 }}
onClick={e => e.stopPropagation()}
>
<p className="text-sm font-semibold" style={{ color: 'var(--text)' }}></p>
<p className="text-xs" style={{ color: 'var(--text-faint)' }}></p>
<div className="flex justify-end gap-2">
<button
onClick={() => setDeleteFolderConfirm(null)}
className="px-3 py-1.5 rounded-lg text-sm"
style={{ color: 'var(--text-muted)' }}
onMouseEnter={e => (e.currentTarget.style.background = 'var(--bg-muted)')}
onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
></button>
<button
onClick={async () => { await deleteFolder(deleteFolderConfirm); setDeleteFolderConfirm(null) }}
className="px-3 py-1.5 rounded-lg text-sm font-medium"
style={{ background: '#ef4444', color: '#fff' }}
></button>
</div>
</div>
</div>
)}
</DndContext>
)
}
@ -489,13 +521,15 @@ function NavItem({ icon, label, count, active, onClick }: { icon: React.ReactNod
)
}
function DroppableFolderItem({ folder, depth, expanded, active, editing, editValue, editRef, onToggle, onClick, onContextMenu, onEditChange, onEditBlur, onEditKeyDown }: {
function DroppableFolderItem({ folder, depth, expanded, active, editing, editValue, editRef, onToggle, onClick, onContextMenu, onEditChange, onEditBlur, onEditKeyDown, onDelete }: {
folder: FolderType; depth: number; expanded: boolean; active: boolean; editing: boolean
editValue: string; editRef: React.RefObject<HTMLInputElement | null>
onToggle: () => void; onClick: () => void; onContextMenu: (e: React.MouseEvent) => void
onEditChange: (v: string) => void; onEditBlur: () => void; onEditKeyDown: (e: React.KeyboardEvent) => void
onDelete: () => void
}) {
const { isOver, setNodeRef } = useDroppable({ id: folder.id })
const [hovered, setHovered] = useState(false)
return (
<div
@ -503,6 +537,7 @@ function DroppableFolderItem({ folder, depth, expanded, active, editing, editVal
className="flex items-center gap-1.5 py-1.5 rounded-lg cursor-pointer text-sm"
style={{
paddingLeft: 8 + depth * 16,
paddingRight: 8,
background: isOver ? 'var(--accent-subtle)' : active ? 'var(--accent-subtle)' : 'transparent',
color: active || isOver ? 'var(--accent)' : 'var(--text-muted)',
outline: isOver ? '2px solid var(--accent)' : 'none',
@ -510,6 +545,8 @@ function DroppableFolderItem({ folder, depth, expanded, active, editing, editVal
}}
onClick={onClick}
onContextMenu={onContextMenu}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<button onClick={e => { e.stopPropagation(); onToggle() }} className="toolbar-btn" style={{ width: 16, height: 16, flexShrink: 0 }}>
{expanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
@ -523,6 +560,18 @@ function DroppableFolderItem({ folder, depth, expanded, active, editing, editVal
) : (
<span className="flex-1 truncate" style={{ fontWeight: active ? 500 : 400 }}>{folder.name}</span>
)}
{hovered && !editing && (
<button
className="toolbar-btn"
style={{ width: 20, height: 20, color: '#ef4444', opacity: 0.8, flexShrink: 0 }}
title="删除文件夹"
onClick={e => { e.stopPropagation(); onDelete() }}
onMouseEnter={e => (e.currentTarget.style.opacity = '1')}
onMouseLeave={e => (e.currentTarget.style.opacity = '0.8')}
>
<Trash2 size={12} />
</button>
)}
</div>
)
}

View File

@ -64,8 +64,12 @@ export function seedIfEmpty(): Promise<void> {
}
async function _doSeed() {
const count = await db.notes.count()
if (count > 0) return
// 用文件夹数量做判断,防止 notes 为空但文件夹已存在时重复创建
const [noteCount, folderCount] = await Promise.all([
db.notes.count(),
db.folders.count(),
])
if (noteCount > 0 || folderCount > 0) return
const folderId1 = crypto.randomUUID()
const folderId2 = crypto.randomUUID()