81 Commits

Author SHA1 Message Date
xfy
975e331dd5 fix(write): split tags on fullwidth punctuation
The tag splitter matched ASCII ',' and ';' twice instead of the
fullwidth ',' (U+FF0C) / ';' (U+FF1B) the comment intended, so tags
separated by fullwidth punctuation (common under Chinese IMEs)
collapsed into a single tag.

Also resolves the compiler warnings surfaced in the same build:
- tiptap_bridge: drop unused Readable/Writable imports and
  EditorInstance/TiptapEditorModule re-exports
- tiptap_bridge: migrate deprecated RequestInit::method/body/
  credentials builders to set_method/set_body/set_credentials
- write: remove redundant `mut` on Dioxus signals that are only
  read/set, never reassigned
- image_viewer: gate the server-only `mut` reassignment so the WASM
  build (which strips the cfg block) doesn't warn
2026-06-23 14:44:18 +08:00
xfy
ef6a3f0da2 fix(home,editor): repair post card nested anchors and editor upload state
首页审查修复(home/post_card/image_viewer/search/home_skeleton):
- PostCard 消除嵌套 <a>:外层 Link 包裹改为绝对定位覆盖层链接,
  标题/摘要/封面降级为普通元素,标签用 z-10 叠在覆盖层之上并
  stop_propagation,整卡点击跳转文章详情。
- ImageViewer 新增 lightbox prop(默认 true),卡片封面传 false
  禁用灯箱以归一为单一跳转交互;文章详情页零改动。
- home.rs 越界页码守卫:分页仅在 total>0 渲染,避免 /page/9999
  出现孤立空分页;错误文案脱敏为"加载失败"。
- search.rs 错误文案脱敏为"搜索失败",与首页/标签页一致。
- HomeSkeleton 卡片 5→10,对齐 POSTS_PER_PAGE。

编辑器上传重构(write/tiptap_bridge):
- consume_upload_event 改用 upload_errors Vec 自身判重,移除
  seen_error_ids 副本状态;Signal 参数加 mut 修复 E0596 编译错误。
- make_upload_closure 构造阶段错误以 rejected Promise 返回而非
  panic,单张坏文件不再拖垮整个编辑器。
- write.rs 移除未用的 success signal,编辑保存后统一跳转 Posts,
  标签分隔符支持半角/全角逗号与分号,提取 META_*_CLASS 常量去重。
- Makefile dev 目标清理 static/ 目录。
2026-06-23 14:14:28 +08:00
xfy
6e98c2abfa fix(blur-up): use slash separator for aspect-ratio (--ar W / H)
根因: CSS aspect-ratio 合法语法是 "width / height"(斜杠分隔),
但 --ar 生成时用了冒号 ":"(--ar:2974:2066)。冒号在 CSS 里是声明分隔符,
2974:2066 被判非法值, aspect-ratio 退化为 auto, 容器高度为 0, 图片不可见.

控制台数据证实: aspectRatio:"auto", clientHeight:0, arVar:"2974:2066"

修复: markdown.rs(正文图) 和 image_viewer.rs(封面) 两处把
{}:{} 改成 {} / {}. CSS 规则 aspect-ratio: var(--ar) 不变,
var 展开后变成合法的 "2974 / 2066".
2026-06-22 18:11:04 +08:00
xfy
1a41f8d7aa feat(image-viewer): render blur-up double-layer structure 2026-06-22 18:00:13 +08:00
xfy
3df677e181 feat(comment): show real relative time on pending comments
待审核评论显示真实相对时间,而非永久"刚刚"

pending_item.rs 改用 format_relative_time_iso 基于创建时间动态计算
相对文本(承接上一个 relative time 重构 commit),并加 title 悬浮提示
显示原始 ISO 时间。修复待审核评论一直显示"刚刚"的问题。
2026-06-22 14:19:05 +08:00
xfy
10ef52bede fix(comment): harden comment form with server honeypot, a11y labels, and reply layout
Some checks failed
CI / check (push) Failing after 5m20s
CI / build (push) Has been skipped
四项评论区输入框修复:

1. 蜜罐服务端二次校验
   - helpers.rs 新增 validate_comment_honeypot 校验器 + 单元测试
   - create_comment 加 honeypot 参数,在限流后做服务端校验
   - 即便机器人禁用 JS 绕过前端拦截,服务端仍返回 spam_detected 错误
   - 与 rate limit 共同构成纵深防御

2. label/input 关联 (可访问性)
   - 四个字段补 id + r#for 属性,用 id_suffix(顶层 root/回复用 parent_id)保证页面唯一
   - textarea 原本无 label,补上"内容 *"label

3. 提交按钮右对齐 + 宽度自适应
   - 新常量 COMMENT_SUBMIT_CLASS 去掉 w-full、px-4 改为 px-6
   - 外层包 flex justify-end,按钮跟随文字宽度
   - login/register 的全宽按钮保持不变(模态卡片场景)

4. 深层回复表单回到内容区左边缘
   - CommentForm 新增 parent_indent prop,回复时用负 margin 抵消父评论缩进
   - 避免 depth 越深表单被越挤越右

验证: cargo test (376 passed) / cargo clippy --all-targets (无警告) / dx check (无问题)
2026-06-22 13:45:28 +08:00
xfy
170c021b37 feat(cache): add PostListItem DTO and use it in list/tag/search caches 2026-06-17 15:51:19 +08:00
xfy
b045a1b978 fix build errors 2026-06-17 10:52:01 +08:00
xfy
449a545886 security: fix critical issues from repository review
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
P0 blockers:
- Fix migration numbering conflict and duplicate indexes
- Change comments.post_id FK to ON DELETE CASCADE
- Restrict public post detail endpoint to published posts only
- Fix rate-limiting IP extraction and fallback to ConnectInfo
- Harden HTML sanitizer: deny unknown URL schemes, restrict data URIs
- Remove session token from login response body
- Enforce image pixel/dimension limits on upload and serving

P1 high-risk:
- Validate uploads by magic bytes and decode GIF/WebP
- Add pagination/rate-limiting to search, tag posts, and comments
- Make first-admin registration and slug uniqueness check atomic
- HTML-escape comment author fields
- Improve HTML minify cache key and skip admin/error responses
- Add mobile navigation menu

P2 accessibility/quality:
- Associate form labels with inputs
- Key PostDetail article by slug to re-init scripts on navigation
- Improve image viewer keyboard accessibility
- Make theme toggle SSR-friendly and add aria-label
- Invalidate slug 404 cache on create and pending count on new comment
- Deduplicate tags case-insensitively

P3 cleanup:
- Remove unused tower-http dependency, expand make clean
- Configure DB pool timeouts and verified recycling
- Run background cleanup tasks immediately on startup
- Use SHA-256 for stable disk cache keys
- Log DB errors with Display instead of Debug
- Update README migration instructions

All tests pass (321), clippy clean, dx check clean.
2026-06-17 10:34:14 +08:00
xfy
603a7f68cb refactor(ui): 提取共享 UI 组件,消除跨页面重复
新增 src/components/ui.rs 作为通用 UI 原子层,封装:

组件:
- <Pagination variant="admin"|"frontend">:合并 posts/comments/trash/home
  四个重复的分页实现,用 variant 区分配色(admin 灰黑 / 前台主题绿)
- <StatusBadge>:统一文章状态、评论状态、回收站剩余天数的徽章外层
- <EmptyState variant="default"|"error">:统一列表空态与加载失败占位

类名常量:
- ADMIN_CARD_CLASS / ADMIN_TABLE_CLASS / ADMIN_ROW_HOVER / CHECKBOX_CLASS
- BTN_SOLID_{GREEN,AMBER,RED}:批量操作实心按钮
- BTN_TEXT_{GREEN,AMBER,RED,ACCENT}:行内文字按钮

清理:
- 删除 input.css 中零引用的 .btn-primary 死代码(22 行)
- 删除 4 个重复的分页组件函数

注意:
- 前台空状态(home/search/archives/tags)保持 text-paper-secondary 配色,
  未强行统一到 admin 的灰色 token,避免改变视觉
- 回收站分页计数单位从"条"修正为"篇"(回收站里是文章)
2026-06-16 14:20:05 +08:00
xfy
4ba4fedb23 feat(ui): 新增 /admin/trash 回收站管理页面与导航 2026-06-16 12:38:41 +08:00
xfy
c43da3676f docs(components): 补充中文注释 2026-06-12 19:42:42 +08:00
xfy
294d60afab style: format rust code
Some checks failed
CI / build (push) Has been cancelled
CI / check (push) Has been cancelled
2026-06-12 17:14:31 +08:00
xfy
835d71972c fix(admin): add missing dark mode background colors to skeleton components 2026-06-12 11:15:46 +08:00
xfy
8dbe564ca2 fix(write): 骨架屏与实际页面结构对齐 2026-06-11 18:12:35 +08:00
xfy
b55409d421 fix(comments): remove admin action buttons from public comment item 2026-06-11 15:47:46 +08:00
xfy
3e95353cc5 fix(comments): nest pending replies under parent comments 2026-06-11 15:35:14 +08:00
xfy
44a738a940 fix(comments): migrate from use_server_future to use_resource 2026-06-11 15:19:37 +08:00
xfy
2b36a7c669 fix: resolve WASM compilation errors
- Remove tracing::warn! (tracing not available in WASM builds)
- Fix parameter names in comment_storage: _key → key, _value → value
- Add #[allow(unused_variables)] for non-WASM builds where params are unused
2026-06-11 14:55:10 +08:00
xfy
aa68dc4c55 fix(comments): address code quality issues in CommentForm
- Add mount guard (loaded signal) to use_effect to prevent re-firing
- Add in-flight guard at top of onclick handler to prevent double submits
- Prevent overwriting user input when clearing form fields
2026-06-11 14:49:48 +08:00
xfy
f855a9a6cd fix(comments): fix compilation errors in list.rs and pending_item.rs
- Fix rsx! macro syntax: can't use let inside for loop
- Revert post_id parameter name and use #[allow(unused_variables)] instead
- All components now compile successfully
2026-06-11 14:45:48 +08:00
xfy
792b06a2eb fix(comments): address code quality issues in list and pending_item
- Add key prop to CommentList for loop for Dioxus list diffing
- Rename post_id to _post_id in PendingCommentItem signature
- Remove unnecessary let _ = post_id suppression
2026-06-11 14:42:03 +08:00
xfy
8ae3299b3e feat(comments): add PendingCommentItem component
Renders pending (unapproved) comments with:
- opacity-70 for visual distinction
- amber '审核中' badge
- Client-side content_md rendering (HTML escape + newline→br)
- No reply button (server rejects replies to pending parents)
- Same depth/indent logic as approved comments
2026-06-11 14:36:05 +08:00
xfy
12a91e3b8e feat(comments): merge approved and pending comments in CommentList
- Accept both comments and pending props
- Merge into chronologically sorted list
- Route to CommentItem or PendingCommentItem per item type
2026-06-11 14:34:36 +08:00
xfy
9e658662cb fix(comments): add error logging for check_pending_status failure
Address code quality review: log warnings when the server call fails
instead of silently swallowing the error.
2026-06-11 14:33:13 +08:00
xfy
40cfd44d3a feat(comments): add pending_comments to CommentContext and sync on mount
- Extend CommentContext with pending_comments Signal
- Load pending comments from localStorage on provider init
- Run check_pending_status on mount to prune non-pending entries
- Pass both approved and pending comments to CommentList
- Include pending count in section heading
2026-06-11 14:30:49 +08:00
xfy
880c53d2a4 fix(comments): show name/email/url fields in reply form 2026-06-11 13:30:54 +08:00
xfy
59d5b9222a refactor(comments): remove privacy consent checkbox and consented param 2026-06-11 13:22:57 +08:00
xfy
04737300e6 feat(comments): add complete comment system with guest commenting, moderation, and admin UI
Implements a fully self-built comment system for the blog:

Data layer:
- comments table with BIGSERIAL PK, parent_id self-reference (ON DELETE SET NULL),
  depth tracking (max 20), status workflow (pending/approved/spam/trash),
  content hashing for dedup, GDPR consent tracking, IP/UA storage with auto-purge
- 5 partial indexes optimized for read patterns
- updated_at auto-trigger

API (9 Dioxus server functions):
- Public: get_comments, get_comment_count, create_comment
- Admin: get_pending_comments, get_pending_count, get_all_comments,
  approve_comment (with ancestor auto-approval), spam_comment, trash_comment,
  batch_update_comment_status

Security:
- Function-level rate limiting (1/sec, burst 5) via FullstackContext IP extraction
- Input validation (name, email, URL scheme, content length, consent)
- Parent chain validation (must be approved, same post)
- Strict comment Markdown renderer (headings→strong, no img/id/data URIs, nofollow links)
- Honeypot anti-spam field
- 5-minute dedup window via SHA-256 content hash

Frontend:
- CommentSection with SuspenseBoundary isolation
- Flat-list rendering with depth-based CSS indentation (responsive)
- Gravatar via cravatar.cn (server-computed, email never exposed)
- Inline reply forms (one-at-a-time via Signal)
- Admin action buttons (approve/spam/delete) visible per-comment
- CommentForm with privacy consent, Markdown hint, loading states

Admin:
- /admin/comments page with status tabs, batch operations, pagination
- Pending count badge on admin dashboard

Infrastructure:
- Shared get_current_admin_user moved from posts/helpers to auth module
- COMMENT_LIMITER rate limiter tier
- Moka caches (60s TTL for comments, 10s for pending count)
- IP/UA purge background task (daily, 90-day retention)
2026-06-11 12:34:26 +08:00
xfy
efa41b42c2 feat(auth): add loading state to login/register forms and update AGENTS.md 2026-06-11 10:44:27 +08:00
xfy
f9d23d1eed refactor(ui): redesign with warm palette, sage accent, and consistent theme vars
- Warm editorial color palette (#faf9f6 light / #141416 dark)
- Sage green accent (#5c7a5e) for nav, links, buttons, tags
- Replace all hardcoded hex colors with CSS theme utilities
- Serif font (system Georgia) only on header logo
- Better hover states: scale, brightness, color transitions
- Accent-colored focus rings on inputs
- Fix language mixing: Back to Home → 返回首页
- No external font dependencies
2026-06-11 10:29:11 +08:00
xfy
9cf6a7e4e6 fix(admin): replace use_delayed_loading with DelayedSkeleton for posts page
Use the same skeleton pattern as Home/Archives/Search pages.
Skeleton shows immediately (static gray blocks) instead of opacity-0 blank,
eliminating white flash during fast loads.
2026-06-10 18:15:46 +08:00
xfy
d3c5653808 feat: redesign admin write page with fixed-height editor layout
- Redesign write page UI with cleaner, more immersive editing experience
- Use CSS variables for theme-aware styling (paper-theme colors)
- Make editor fill remaining viewport height with internal scrolling
- Move action bar below editor (left-aligned, non-floating)
- Fix layout: page no longer scrolls, only editor content scrolls
- Update write skeleton to match new layout
- Update admin layout to use h-dvh for write routes to prevent page scroll
- Fix Dioxus.toml dev resource loading to include style.css

Fixes from review:
- Add missing relative positioning for loading overlay
- Use consistent root_class in unauthenticated state
- Use h-dvh instead of h-screen for mobile viewport compatibility
- Ensure skeleton matches actual component layout classes
2026-06-10 17:41:31 +08:00
xfy
2f433e8f11 feat: add 404 Not Found page
- Add NotFound component with elegant design
- Register catch-all route in router
- Add skeleton placeholder for NotFound route
- Remove unused design-taste-frontend skill
2026-06-10 15:43:05 +08:00
xfy
0319c927c6 chore: format code 2026-06-09 13:43:10 +08:00
xfy
44c1358da5 refactor: extract post content JS into standalone script 2026-06-09 13:18:40 +08:00
xfy
0345fa70f2 fix: add click handler for code block copy button 2026-06-09 11:33:26 +08:00
xfy
42b39266a6 fix: preserve data URI in img src during markdown rendering
ammonia's default url_schemes whitelist excludes 'data', causing
data:image/* URLs to be stripped from img src attributes. Add 'data'
to allowed schemes. Also skip data: URIs in JS thumbnail logic.
2026-06-09 10:06:34 +08:00
xfy
185bcab9f9 cleanup(forms): remove unused BUTTON_SECONDARY_CLASS 2026-06-08 16:19:35 +08:00
xfy
717266db1e fix: resolve conditional compilation and dead code warnings 2026-06-08 16:11:24 +08:00
xfy
b6f41e74e7 feat: image thumbnail + lightbox viewer
- Add ImageViewer reusable component with thumbnail and click-to-zoom
- PostCover: load ?w=1200 thumbnail, click to view full-size
- PostCard: display cover thumbnail (?thumb=400x300) in list view
- PostContent: inline images load ?w=800 thumbnail, click to zoom
- Add lightbox overlay styles with fade-in animation
- Add zoom cursor and hover effect for zoomable images
- Extend web-sys features for DOM image/lightbox manipulation
2026-06-08 15:52:47 +08:00
xfy
c776536678 fix(write-skeleton): align skeleton layout with actual write page
- fix bottom button bar layout (justify-end -> items-center with flex-1 spacer)
- remove extra p-6 padding from editor container
- add p-4 padding to toolbar area to match Tiptap editor padding
2026-06-05 18:21:35 +08:00
xfy
077573af67 fix(layout): highlight write nav for both /write and /write/:id 2026-06-05 17:55:18 +08:00
xfy
6d621d9ec4 feat(admin): remove '前台' nav item from admin header 2026-06-05 17:07:22 +08:00
xfy
28d1ee0d52 fix: reset UserContext on login/logout to prevent admin page stuck on re-login
Root cause: UserContext.checked was never reset on logout, causing AdminLayout's use_effect to skip get_current_user() on subsequent mounts, leaving the page in (checked=true, user=None) limbo.

- Reset ctx.user and ctx.checked on logout in admin_layout.rs
- Reset ctx.checked on login success in login.rs so AdminLayout re-validates session on mount
2026-06-05 10:42:20 +08:00
xfy
7866c53e36 feat: add dark mode toggle button to admin layout 2026-06-05 10:33:34 +08:00
xfy
5321b8cf97 refactor: reorganize admin write page layout with card-based sections 2026-06-05 09:58:26 +08:00
xfy
245fd83ec8 feat: replace in-footer scroll-to-top link with fixed floating button 2026-06-04 17:55:26 +08:00
xfy
1830fc9f2e cleanup: remove dead code, debug prints, and unused dead_code allow attributes 2026-06-04 16:20:43 +08:00
xfy
4e33bd3ee6 refactor: extract form atoms (FormInput, FormLabel, AlertBox) and replace repeated input/button styles 2026-06-04 15:55:40 +08:00