175 Commits

Author SHA1 Message Date
xfy
64ad40ca61 fix(admin/posts): Tooltip 包裹后操作按钮恢复垂直居中
- Tooltip 外层 div 由块级改为 inline-flex,作为行内伸缩盒参与
  flex 行对齐,不再撑高或破坏基线
- 操作列容器补 items-center,显式锁定所有子项垂直居中
2026-07-03 11:08:04 +08:00
xfy
73b9cad550 refactor(ui): 抽取 Tooltip 组件,行内重建按钮补 tooltip
新增可复用 Tooltip 组件(group-hover 显现,支持 top/bottom 朝向),
消除顶部重建按钮手写的内联 tooltip div 重复,并为表格行内重建
按钮补上 tooltip(朝上,避免被表格容器的 overflow-hidden 裁掉)。
2026-07-03 11:03:56 +08:00
xfy
a5c00caa80 style(admin/posts): 操作列加宽横排,重建按钮 loading 用 spinner 图标
- 操作列宽 w-24 → w-44,三个按钮一行排开不换行
- 重建按钮 loading 态由「重建中...」文案改为 spinner 图标 + 「重建中」
- spinner 复用 public/icons/90-ring-with-gradient.svg,作为 SPINNER_SVG
  常量收入 ui.rs(修正原 SVG 损坏的孤立 ':' 选择器,重命名 id/类名
  避免多处内联时冲突),用 currentColor 继承按钮文字色
2026-07-03 10:26:14 +08:00
xfy
65b15509a7 chore(wasm): 清理 wasm32 target 下残留的 clippy lint
这些 lint 在 server build(默认 --all-features)下被 cfg 剥离故未暴露,
仅 wasm32 web-only target 触发。逐项清理:

- footer.rs: 两处 redundant closure(web_sys::window 直接传函数引用、
  sync_visible 直接传闭包)+ let _ = unit 绑定(scroll_to_*)。
- hooks/event_listener.rs: 复杂类型 Rc<RefCell<Option<(Closure,T)>>>
  抽 type ListenerState<T> 别名。
- system.rs: 两处单模式 match { Ok=>.., _=>{} } 改 if let。
- write.rs: split(|c| matches!(c,...)) 改 split([char; N]) 数组模式。
- codemirror_bridge.rs: let _ = instance.destroy() 删 unit 绑定,
  与 tiptap_bridge.rs 同处写法对齐。

wasm32 与 server 两 target clippy -D warnings 均通过。
2026-07-02 17:15:27 +08:00
xfy
fee5b7ba94 refactor(wasm): 消除 DOM 互操作中的 js_sys::eval 字符串求值
对照 Dioxus "Breaking Out" 文档审查后,将 3 处 js_sys::eval 改为
类型化调用,与 tiptap_bridge/codemirror_bridge 风格统一:

- utils/time.rs sleep_ms:eval("new Promise(r=>setTimeout(r,n))")
  改为 js_sys::Promise + web_sys::Window::set_timeout_*,复用项目内
  system.rs::wasm_sleep 同款写法。
- theme.rs 主题切换:format!-into-eval 拼 __startThemeTransition(x,y)
  改为 Reflect::get 取函数 + Function::call2,消除字符串注入面
  (原坐标虽为 f64 无实际风险,但属不必要 eval)。
- post_content.rs:__initPostContent / __initLightbox 两处 eval 字面量
  改为 invoke_optional_global 辅助函数(Reflect::get + Function::apply),
  消除旧版 "eval 字符串粘贴" 模式;__lightboxSelectors 直接复用 JsValue
  传入,省去一次回读。
2026-07-02 17:15:18 +08:00
xfy
2b6b2ca9e4 fix(hooks): 修复 wasm32 target 编译失败
之前验证只跑了 server target(cargo build --all-features),漏掉 wasm32
前端 target,导致三类错误未被捕获:

1. event_listener.rs wasm 分支缺 use dioxus::prelude::*:
   use_hook/use_effect/use_drop 来自 prelude,之前重写时误删。补回。

2. event_listener.rs use_effect 闭包 move 冲突(E0507/E0310):
   use_effect 回调是 FnMut,但 acquire 是 FnOnce、handler 要 move 进事件
   闭包。用 Option::take 模式包裹,首次运行消费,避免重复 move。同时给
   A 加上 'static 约束。

3. query.rs items/total/footer.rs sync_visible 缺 mut:
   wasm 分支的 .set() 调用需要 mut 绑定。之前为消 server target 的
   unused_mut 警告误删,改用 cfg_attr 在非 wasm 时压制警告。

教训:fullstack 项目必须同时验证 wasm32 与 server 两个 target,单一
target 通过不代表整体可用。
2026-07-02 16:43:15 +08:00
xfy
759bbab7ee refactor(utils): comment_storage 从 hooks/ 迁至 utils/
该文件全是普通函数(save_author / load_pending_comments 等),没有任何
use_ 开头的 Hook,放在 hooks/ 下名不副实。内部只剩结构体与 localStorage
读写(时间/HTML 工具已在上一 commit 拆出),迁到 utils/ 后文件名与内容
自洽,hooks/ 目录回归只放真正的渲染期 Hook。

机械替换 4 个组件文件的 use 路径(hooks::comment_storage → utils::comment_storage)。
2026-07-02 16:18:52 +08:00
xfy
dcdd72a8c2 refactor(utils): 拆分 comment_storage 的时间/HTML 工具函数
消除 server 端对 hooks 模块的跨层引用:

- escape_html(+ 两份重复实现:comment_storage 用 &#39;、helpers 用 &#x27;)
  统一到 utils/html.rs,单引号采用 HTML5 标准 &#x27;。
- now_millis / relative_label_from_millis / format_relative_time_iso 及其
  单元测试迁到 utils/time.rs,与既有 sleep_ms 同构(wasm32/非 wasm 双版本)。

调用方迁移:
- api/comments/helpers.rs:删掉本地 escape_html,format_relative_time 改引
  utils::time;其 escape_html 测试随之删除(utils/html.rs 已覆盖)。
- api/comments/create.rs:两处 helpers::escape_html 改 utils::html。
- api/markdown.rs:escape_heading_text 改 utils::html。
- components/comments/pending_item.rs:format_relative_time_iso 改从
  utils::time 引入;render_pending_content 留在 comment_storage(语义紧贴
  待审核评论)。
- hooks/comment_storage.rs:is_expired 的 now_millis 改引 utils::time。

comment_storage.rs 现在只剩结构体与 localStorage 读写,文件名即将自洽。
2026-07-02 16:15:23 +08:00
xfy
f00c2bf963 refactor(hooks): 新增 use_event_listener 通用事件监听 hook
把 theme.rs 与 footer.rs 两处手写的 "use_hook 持有 Closure+target、
use_effect 注册 add_event_listener、use_drop 移除" 样板收口到一个
通用 hook,按 Dioxus 0.7 custom hooks 范式封装。

hook 设计:
- target 通过 acquire 闭包在 use_effect 首次运行时获取(此时 DOM 一定
  可用),调用方无需再自己包 use_effect。
- 泛型 AsRef<EventTarget> 统一 Window / Element / MediaQueryList。
- 非 wasm32 整体编译为 noop,调用方用 #[cfg] 隔离 web_sys 调用即可。

footer 的 "注册后首次同步" 业务逻辑保留在 hook 之外(语义不属于
事件监听)。theme 的 handler 改为重新 match_media 读取 matches,
功能等价(低频事件,开销可忽略)。
2026-07-02 16:02:51 +08:00
xfy
48a21564c4 refactor(assets): 背景图固定 opacity-10,移除动态透明度逻辑
动态判断不生效,统一固定为 10% 透明度,移除聚焦/有内容的条件类。
2026-07-02 11:17:41 +08:00
xfy
5fd883d7a1 feat(comments): 评论区有内容时自动调淡背景图透明度
与 tiptap 编辑器保持一致:无内容时 60%,有内容或聚焦时淡出到
10%。content_md 由 oninput 实时维护,输入即触发重渲染。
2026-07-02 11:14:58 +08:00
xfy
48107d8e18 refactor(assets): 评论区与后台编辑器背景图换用 xiantiaoxiaogou_input_bg 2026-07-02 10:58:52 +08:00
xfy
78f3ff4b2f fix(admin): 登出按钮补 cursor-pointer
登出按钮缺少 cursor-pointer,鼠标悬停时仍显示文本光标而非手型,与
其他可点击元素不一致。
2026-07-01 17:00:58 +08:00
xfy
3faff98dc8 fix(layout): 后台导航栏与正文宽度对齐
Header 的导航宽度写死 max-w-3xl(前台阅读宽度),后台直接复用导致
导航栏(768px)比正文(max-w-5xl=1024px)窄 256px,左边缘错位。

给 Header 加 max_width prop(默认 max-w-3xl 保持前台行为不变),后台
两处调用点传 max-w-5xl,与 admin_layout 的 main 同宽对齐。
2026-07-01 17:00:58 +08:00
xfy
90734446b5 refactor(ui): remove redundant Tabs component
Tabs 现已无调用方(/admin/system 已改用 FilterTabs),删除以消除 DRY 违反。
全局只保留 FilterTabs 一个 tab 组件,动画/样式统一。
2026-06-30 18:12:03 +08:00
xfy
bf91d18c4f refactor(admin): use shared Tabs component in /admin/system
移除 System 组件内联的 tab 按钮,改调公共 Tabs 组件。
active_tab signal 经 SystemTab::as_str/from_str 与 Tabs 的 String API 桥接,
match 穷举面板选择逻辑不变。视觉/行为零变化,只消除重复。

同时移除 Task 1/2 加的临时 #[allow(dead_code)](as_str/from_str/Tabs 现已有调用方)。
2026-06-30 14:29:05 +08:00
xfy
ff7d9e1e46 feat(ui): add Tabs component for page-level navigation
与 FilterTabs(列表筛选 + 滑动动画)并列,职责区分:
- FilterTabs:同质数据过滤(全部/待审/已通过)
- Tabs:切换到结构不同的面板(数据库/服务器/SQL 控制台)

选中态用按钮自身 border-b-2 下划线(无动画)。
API 与 FilterTabs 对齐(String value + EventHandler),便于调用方迁移。
2026-06-30 14:16:36 +08:00
xfy
81a4196e43 feat(admin): add /admin/system route shell with 5 tabs
新增系统管理入口页(数据库状态/服务器状态/SQL 控制台/导出/备份恢复),
顶部 tab 切换(用 use_signal 不深链)。路由注册 + 导航加「系统」项。
tab 内容待后续 task 填充。
2026-06-29 18:26:24 +08:00
xfy
45811df96e fix(comments): add md-content class to comment item to fix syntax highlighting and blank line issues 2026-06-29 15:42:08 +08:00
xfy
656e35c79f fix(comments): move decorative image properly inside textarea background layer 2026-06-29 15:20:48 +08:00
xfy
0b53e968c1 feat(comments): add decorative image to comment input with focus fade effect 2026-06-29 14:42:03 +08:00
xfy
d1b5b8ee4e fix(ui): suppress unused variable warning for active param in server build 2026-06-29 14:29:45 +08:00
xfy
373498870a style: apply cargo fmt to workspace
Some checks failed
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
xfy
c69a632e75 fix(ui): trigger FilterTabs indicator animation on click 2026-06-29 13:24:52 +08:00
xfy
3daa09efc2 fix(ui): fix WASM compilation error in FilterTabs indicator animation 2026-06-29 13:22:22 +08:00
xfy
ec5f933191 feat(ui): add smooth sliding animation to FilterTabs active indicator 2026-06-29 13:20:39 +08:00
xfy
d91a56f8b2 refactor(admin): extract comments filter tabs into FilterTabs component 2026-06-29 13:15:50 +08:00
xfy
178870427c feat(ui): refactor EmptyState to accept dynamic string props and add empty state to archives page 2026-06-29 10:11:00 +08:00
xfy
ac1f92d816 refactor(core): 迁移 post-content 到 yggdrasil-core,删除 public/js
逐行 TypeScript 化(逻辑不变),改走 window.__initPostContent 全局入口。
post-content.js 已迁移进 libs/yggdrasil-core/,public/js/ 目录随之删除。
2026-06-26 13:57:48 +08:00
xfy
0398cc6c66 perf(render): 列表加显式 key + release 启用 panic=abort
依据 Dioxus 0.7 optimizing 指南做两项改进:

- 为数据驱动列表渲染补全显式 key(13 处,9 个文件):
  home/archives/tags/search 文章与标签列表、post_card/post_footer
  标签、header 桌面/移动导航、admin dashboard/posts/write 列表。
  使用各类型稳定唯一字段(post.id / tag / tag.name / item.label /
  err.id / year_group.year / month_group.month_en)作为 key,
  让 Dioxus diff 正确识别元素身份。骨架屏 for _ in 0..N 静态占位
  循环与已带 key 的评论树/admin 行不动。

- [profile.release] 加 panic = "abort":WASM 去掉 unwind 元数据减小
  体积;server 端错误处理走 Result+?(见 error.rs),不依赖 panic
  unwind,无副作用。

其余文件为本机格式化(单行折叠/多行展开),一并提交。

验证:dx check 通过、cargo test 405 passed。
2026-06-25 18:07:22 +08:00
xfy
d41eb4b3cd refactor(write): 滚动条从内容区内部移到窗口边缘
将 /admin/write 从「锁一屏 + 内容区内部滚动」改为「整页自然滚动」,
滚动条出现在浏览器窗口右侧而非中间内容区边缘。

admin_layout: write 路由 root 由 h-dvh overflow-hidden 改为 min-h-dvh
自然文档流,main 去掉 overflow-hidden。

write.rs: 根 div 与内容区去掉 flex-1/min-h-0/overflow-y-auto 等滚动
约束;编辑器高度由 flex-1 改为 h-[60vh](min-h-[400px] 兜底);底部
操作栏去掉 flex-shrink-0,改为跟随页面滚动。

整页滚动时 Header 仍 sticky 吸顶(带毛玻璃),保存栏随内容滚走。
2026-06-25 16:24:26 +08:00
xfy
806b474599 feat(theme): 新增冷调玫瑰第二色用于次要按钮
在 input.css 的 @theme 新增 accent-2 / accent-2-soft 主题变量,
用于与鼠尾草绿主色成对的次要操作按钮。

颜色选择经 HSL 分析:色相 342°(冷调玫瑰,区别于暖砖红 9°),
饱和度 28%(低饱和从属于主色),保证视觉为浅色系且不浊。
对比度:亮色 #83495b vs 米白底 6.5:1、暗色 #cca4b0 vs entry 底 7.46:1,
均过 WCAG AA。

新增 BTN_SECONDARY 常量(ghost 描边风格)用于 dashboard「管理文章」
按钮,与「写文章」实心主按钮构成主次层级。两按钮统一用
inline-flex items-center justify-center 保证文字水平垂直居中、等高。
2026-06-25 15:06:45 +08:00
xfy
519c0ae3a9 feat(admin): 后台迁移至 paper-* 主题变量对齐前台
将 /admin 路由下硬编码的 Tailwind gray + hex 配色统一迁移到
input.css 定义的 paper-* 主题变量系统,使后台视觉完全对齐前台:

- 底色:bg-white → bg-paper-theme(米白,与前台一致)
- 卡片/表格:bg-white dark:bg-[#2e2e33] → bg-paper-entry
- 主操作按钮(写文章/保存/分页):bg-gray-900 黑 → bg-paper-accent 鼠尾草绿
- 文字/边框/hover:gray-* + dark:hex → text-paper-*/border-paper-*
- 清理 trash 页散落的 #5c7a5e/#7da97f/#e8f0e8 等 hex,路由到变量
- 骨架占位统一复用 ADMIN_* 常量与 SkeletonBox

保留 green/amber/red 状态语义色(通过/待审/删除)与 toggle 白色滑块。
统一采用 bg-paper-* 工具类语法,与 header/post_card 主流写法一致。

dx check + clippy + 402 tests 全通过。
2026-06-25 14:33:48 +08:00
xfy
df85df3d2e style(empty-state): 配图加圆角,暗色模式降低亮度
- rounded-lg 圆角(= 项目的 --radius-paper 8px)
- dark:brightness-90 滤镜,让彩色配图在暗色主题下退后一些
2026-06-25 13:48:46 +08:00
xfy
cfd2af1fab refactor(empty-state): 内联 SVG 换成 webp 配图
移除手绘的 LineDog 内联 SVG,改用 <img> 引用
public/images/xiaotiaoxiaogou_01.webp 原始插画。
2026-06-25 13:43:23 +08:00
xfy
8b581649a6 feat(home): 用「线条小狗」插画替换首页空状态
原空状态仅有一行「暂无文章」文案。新增 EmptyState 组件:
- 内联单色 SVG 配图(双手持相机,取景器内两只小狗),由 webp 重绘为线条画
- 描边用 currentColor 适配明暗主题,屏幕区填 accent-soft 作焦点
- 标题改「还没有文章」,副文案呼应站点定位
- 保留可选 action 字段供后台上下文复用

颜色、圆角、入场动画均沿用项目 Forest 调色板与既有视觉语言。
2026-06-25 13:37:07 +08:00
xfy
21322a4339 fix(card): 提升覆盖层 z-index 使封面图可点击跳转
封面完整图 .blur-img-full 在全局 CSS 有 z-index:1 (input.css:611),
盖在原 z-0 的覆盖层 Link 之上,导致 hover 封面命中 <img> 而非 <a>:
光标非 pointer 且点击不跳转。

将覆盖层提到 z-[2](>封面图 1,<标签 10),命中 <a> 后光标与点击一并解决,
标签 stop_propagation 与 hover 缩放不受影响。
2026-06-25 11:10:43 +08:00
xfy
71014aa0de style: 封面图比例统一改为 21:9 超宽
卡片 16:9 仍偏高,统一改为 21:9(超宽荧幕比例),前后台一致:
- input.css: .blur-img.post-card-cover-blur aspect-ratio 16/9 → 21/9
- post_card.rs: 缩略图 thumb=400x225 → 420x180 匹配 21:9
- write.rs: 编辑器封面区 aspect-video → aspect-[21/9]
2026-06-24 15:53:55 +08:00
xfy
5fc8a9aeeb style(card): 卡片封面图比例从 4:3 改为 16:9
- input.css: .blur-img.post-card-cover-blur 的 aspect-ratio 4/3 → 16/9
- post_card.rs: 缩略图请求 thumb=400x300 → 400x225 匹配 16:9
2026-06-24 15:49:53 +08:00
xfy
0301185a17 refactor(post-content): drop lightbox include_str!, use config-driven init 2026-06-24 11:07:24 +08:00
xfy
cb8c42e833 refactor: remove obsolete ImageViewer component
both call sites (PostCover, PostCard) now render raw .blur-img, so the
Dioxus ImageViewer component — with its leaky closure.forget() and dead
js_sys::eval delay — is no longer needed. remove the file and its mod
declaration.
2026-06-23 15:51:40 +08:00
xfy
8b1d55613e refactor(post-card): use raw blur-img for cover, drop ImageViewer
card cover is display-only (click goes to the card link), so it uses
a bare .blur-img with ?w=20 placeholder + ?thumb=400x300 display, no
lightbox-single class. card pages are outside .post-content/.entry-cover
so lightbox.js never scans them.
2026-06-23 15:50:29 +08:00
xfy
75b9080df8 refactor(post-cover): use raw blur-img structure as single lightbox image
drop ImageViewer; render .blur-img with a lightbox-single class (the
class, not a data attribute, because Dioxus' typed span elements reject
arbitrary data-* attrs). lightbox.js now keys single-mode off the
lightbox-single class instead of data-single. cover uses ?w=20
placeholder + ?w=1200 display; SSR writes --ar from real dimensions.
2026-06-23 15:48:02 +08:00
xfy
f88d83b0ad feat(post-content): load lightbox.js and init gallery
eval both post-content.js (copy) and lightbox.js (lightbox + lazy-load),
then call __initLightbox over ['.post-content', '.entry-cover'] so both
in-content images (gallery) and the cover (single) are wired up.
2026-06-23 15:45:30 +08:00
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