+
+
+
+
+ setOldPw(e.target.value)}
+ />
+
-
-
- setNewPw(e.target.value)}
- className="mt-1 w-full rounded-lg border border-zinc-300 bg-transparent px-3 py-2 text-sm outline-none focus:border-blue-500 dark:border-zinc-700"
- />
-
-
-
- {msg &&
{msg}}
+
+
+
+
+ setNewPw(e.target.value)}
+ />
+
+
+
新密码长度至少 12 个字符,建议混合字母、数字与符号。
+
+
+
)
}
diff --git a/web/src/pages/AdminTrash.tsx b/web/src/pages/AdminTrash.tsx
index 54323e1..a697dd5 100644
--- a/web/src/pages/AdminTrash.tsx
+++ b/web/src/pages/AdminTrash.tsx
@@ -1,12 +1,18 @@
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { RotateCcw } from 'lucide-react'
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
+import { Info, RotateCcw } from 'lucide-react'
+import { useState } from 'react'
import { api } from '../lib/api'
import type { NoteItem } from '../lib/api'
import { invalidatePublic } from '../lib/invalidate'
-import { formatDateTime } from '../lib/utils'
+import { useDocumentTitle } from '../features/site'
+import { daysRemaining, formatDateISO } from '../lib/utils'
+import { AdminTopbar } from '../components/Layout'
+import Toast from '../components/Toast'
export default function AdminTrash() {
+ useDocumentTitle('回收站')
const qc = useQueryClient()
+ const [toast, setToast] = useState<{ msg: string; kind: 'success' | 'error' } | null>(null)
const { data, isLoading } = useQuery({
queryKey: ['admin', 'trash'],
queryFn: () => api<{ items: NoteItem[]; total: number }>('/api/admin/trash'),
@@ -15,45 +21,83 @@ export default function AdminTrash() {
const restore = useMutation({
mutationFn: (id: number) => api(`/api/admin/trash/${id}/restore`, { method: 'POST' }),
onSuccess: () => {
+ setToast({ msg: '笔记已恢复', kind: 'success' })
void qc.invalidateQueries({ queryKey: ['admin'] })
invalidatePublic(qc)
},
+ onError: () => setToast({ msg: '恢复失败,请重试', kind: 'error' }),
})
+ const items = data?.items ?? []
+
return (
-
-
回收站({data?.total ?? 0})
-
软删除的笔记保留 30 天,到期由每日 gc 物理清除;恢复后图片引用天然保全。
- {isLoading ? (
-
加载中…
- ) : !data || data.items.length === 0 ? (
-
回收站是空的
- ) : (
-
- {data.items.map((n) => (
- -
-
- {n.status === 'public' ? '公开' : '私有'}
-
-
-
{n.title}
-
- /{n.slug} · 删除于 {formatDateTime(n.deleted_at ?? 0)}
-
-
-
-
- ))}
-
- )}
- {restore.isError &&
恢复失败,请重试
}
-
+ <>
+
+
+
+
+
+
回收站中的笔记会在删除 30 天后永久删除,请及时恢复需要的内容。
+
+
+
+ {isLoading ? (
+ 加载中…
+ ) : items.length === 0 ? (
+ 回收站是空的
+ ) : (
+
+
+
+
+ | 标题 |
+ 删除时间 |
+ 剩余天数 |
+ 操作 |
+
+
+
+ {items.map((n) => {
+ const left = n.deleted_at ? daysRemaining(n.deleted_at) : 0
+ return (
+
+ |
+ {n.title}
+ /{n.slug}
+ |
+
+ {n.deleted_at ? formatDateISO(n.deleted_at) : '—'}
+ |
+
+ {left} 天
+ |
+
+
+
+
+ |
+
+ )
+ })}
+
+
+
+ )}
+
+
+ 共 {data?.total ?? 0} 篇已删除笔记
+
+
+
+
+
setToast(null)} />
+ >
)
}
diff --git a/web/src/pages/Home.tsx b/web/src/pages/Home.tsx
index 3b1c643..544528c 100644
--- a/web/src/pages/Home.tsx
+++ b/web/src/pages/Home.tsx
@@ -1,18 +1,17 @@
import { useState } from 'react'
import { Link } from 'react-router'
import { useQuery } from '@tanstack/react-query'
-import { Pin } from 'lucide-react'
+import { ArrowRight, ChevronLeft, ChevronRight, Pin } from 'lucide-react'
import { api } from '../lib/api'
import type { NoteItem, NoteList, TagCount } from '../lib/api'
-import { formatDate } from '../lib/utils'
+import { useDocumentTitle } from '../features/site'
+import { formatDateISO } from '../lib/utils'
-function useNotes(page: number, tag?: string) {
+function useNotes(page: number) {
// 不传 page_size:服务端默认值即站点设置(管理员可改),首页随设置生效
- const qs = new URLSearchParams({ page: String(page) })
- if (tag) qs.set('tag', tag)
return useQuery({
- queryKey: ['notes', page, tag ?? ''],
- queryFn: () => api(`/api/notes?${qs}`),
+ queryKey: ['notes', page],
+ queryFn: () => api(`/api/notes?page=${page}`),
})
}
@@ -23,40 +22,28 @@ function useTags() {
})
}
-function NoteCard({ note }: { note: NoteItem }) {
+function NotePost({ note }: { note: NoteItem }) {
return (
-
-
+
+
-
-
+ {formatDateISO(note.updated_at)}
+
+
+
{note.title}
+
- {note.summary && (
-
- {note.summary}
-
- )}
+ {note.summary && {note.summary}
}
{note.tags.length > 0 && (
-
+
{note.tags.map((t) => (
-
- #{t}
-
+ {t}
))}
)}
@@ -66,62 +53,76 @@ function NoteCard({ note }: { note: NoteItem }) {
export default function Home() {
const [page, setPage] = useState(1)
+ useDocumentTitle()
const { data, isLoading } = useNotes(page)
const { data: tagsData } = useTags()
const totalPages = data ? Math.max(1, Math.ceil(data.total / data.page_size)) : 1
+ const topTags = (tagsData?.tags ?? []).slice(0, 4)
return (
-
-
-
+
+
+ PURE NOTE · 极简笔记
+ 记录,而不打扰。
+ Pure Note 是一个纯文本驱动的极简笔记工具,这里是公开的笔记。
+
+
+ {topTags.length > 0 && (
+
+ )}
+
+
{isLoading ? (
- 加载中…
+ 加载中…
) : !data || data.items.length === 0 ? (
- 还没有公开的笔记
+ 还没有公开的笔记
) : (
- data.items.map((n) => )
- )}
- {totalPages > 1 && (
-
+ data.items.map((n) => )
)}
+
+ {totalPages > 1 && (
+
+ )}
)
}
diff --git a/web/src/pages/Login.tsx b/web/src/pages/Login.tsx
index d535c3d..5112982 100644
--- a/web/src/pages/Login.tsx
+++ b/web/src/pages/Login.tsx
@@ -1,13 +1,16 @@
import { useState } from 'react'
-import { useNavigate } from 'react-router'
-import { Lock } from 'lucide-react'
+import { Link, useNavigate } from 'react-router'
+import { ArrowLeft, ArrowRight, CircleAlert, Eye, EyeOff, Lock } from 'lucide-react'
import { useAuth } from '../lib/auth'
import { ApiError } from '../lib/api'
+import { useDocumentTitle } from '../features/site'
export default function Login() {
+ useDocumentTitle('管理员登录')
const { login, authenticated } = useAuth()
const navigate = useNavigate()
const [password, setPassword] = useState('')
+ const [showPw, setShowPw] = useState(false)
const [error, setError] = useState(null)
const [submitting, setSubmitting] = useState(false)
@@ -24,7 +27,9 @@ export default function Login() {
navigate('/admin', { replace: true })
} catch (err) {
if (err instanceof ApiError) {
- setError(err.status === 429 ? '尝试过于频繁,请稍后再试' : '密码错误')
+ if (err.status === 429) setError('尝试过于频繁,请稍后再试')
+ else if (err.status === 403) setError('请求被安全策略拦截,请刷新页面后重试')
+ else setError('密码错误,请重试')
} else {
setError('登录失败,请重试')
}
@@ -34,38 +39,60 @@ export default function Login() {
}
return (
-