918 Commits

Author SHA1 Message Date
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
d11385e3ee docs(agents): 沉淀双 target 验证与 custom hook 踩坑记录
Testing 章节补充:fullstack 项目必须同时验证 server 与 wasm32 两个
target,给出 wasm 编译命令;说明 dx check 不等于完整 cargo wasm
build,会漏掉 borrow/move 错误;列出三类常见 target-mismatch 陷阱
(cfg 不可见、unused_mut 误删、函数内 use 作用域)。

新增 Pitfall Log 章节,记录本次重构踩过的四个坑:
- 持有资源的 custom hook(use_hook+use_effect+use_drop)里 FnMut 闭包
  move FnOnce 参数的 E0507/E0310,用 Option::take 模式解决;
- 单 target 验证的危害(只跑 cargo build --all-features 会漏 wasm 错误);
- 仅 wasm 需要的 mut 绑定用 cfg_attr 压制非 wasm 的 unused_mut;
- 往 utils 模块搬函数前先查目标模块的 feature gate(text.rs 是
  server-only 且依赖 regex,前端可达函数不能进)。
2026-07-02 16:49:48 +08:00
xfy
ba5b146dfa fix(theme): 消除 detect_initial_theme 的 unreachable_code 警告
wasm 分支末尾的 return Theme::System 在 wasm target 下让尾部兜底
Theme::System 不可达(unreachable_code 警告)。把尾部兜底用
#[cfg(not(target_arch = "wasm32"))] 限定,使其只在 server 分支
(cookie 未命中)或边角构建时编译,wasm target 下函数以 wasm
分支的 return 自然结束,两端都零警告。
2026-07-02 16:49:31 +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
291189fb77 fix(sanitizer): 折叠嵌套 if 修复 clippy::collapsible_if
865871f 引入的 checkbox type 白名单逻辑用了嵌套 if,触发 rust 1.96
的 clippy::collapsible_if,导致 make clippy 直接失败。按 clippy 建议
合并为单个 if 链,行为不变。
2026-07-02 16:36:05 +08:00
xfy
59359b18ce refactor(hooks): 新增 use_paginated 并迁移 posts/trash 分页加载
按 Dioxus 0.7 custom hooks 范式,把后台分页列表页反复出现的
use_effect + spawn + (loading/items/total/error) 三件套收口到
use_paginated 组合式 hook。统一非 wasm cfg 处理(SSR 不发请求、
loading 立即置 false),调用方只关心 fetch 闭包与页码依赖。

迁移范围(按降级预案,只做最规整的两处):
- pages/admin/posts.rs:顺手补 error signal(原先吞掉 Err),向
  trash 看齐,消除一致性债。
- pages/admin/trash.rs:直接替换,error 已有。

保留手写的形态(强行套 hook 反而更复杂):
- comments.rs:依赖 (page, status) 双 signal,status 是额外维度,
  套不进 (page, per_page) 模型;改 hook 会为单一调用点变复杂。
- system.rs 三个 tab:load_once + use_future 轮询形态,与 hook 的
  单次加载模型不契合。
- dashboard.rs:多源聚合(3 个并发 spawn 分别 set 不同 signal)。

list_posts / list_deleted_posts 是 server function,两端都生成
(wasm 端 client stub、server 端真实实现),故去掉原 cfg import。
2026-07-02 16:30:47 +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
5a00ef31f3 docs(agents): update agents for JavaScript 2026-07-02 15:45:42 +08:00
xfy
0772951867 fix(editor): 修复升级后空项 Enter 不退出列表(根因:畸形文档)
bug 表现:- [ ] 123 升级成任务列表后,空项按 Enter 不退出,而是继续新建空项。

根因有两处,都在 appendTransaction 构造新节点时:

1. stripPrefix 作用域错误:原代码 cut 作用在 listItem.content(把段落节点
   当原子),cut(N) 会切进段落的标签边界,产生畸形文档——段落文本前缀残留
   (如' 未完成'多出空格)、nodeSize 与实际不符。修正为作用在段落内部的
   content 上(用 para.type.create 重建段落),并用 Fragment.replaceChild
   替换 listItem 的第一个子节点。

2. 光标位置少算一层:+1 进 taskList 内容,+offset 到命中 taskItem,+1 进
   taskItem 内容(段落节点位置),还需 +1 进段落内容(文本),原代码漏了
   这最后一个 +1,导致光标落在文本中间而非末尾,splitListItem 在错误位置
   分裂(把'123'拆成'12'+'3')。

畸形文档 + 光标错位 → splitListItem 在错误位置分裂,产生的'空'taskItem
实际不空,二次 Enter 走分裂路径而非退出路径。

新增回归测试:段落文本无残留 + 完整 Enter 链路(升级→Enter 空项→Enter 退出)。
2026-07-02 15:27:37 +08:00
xfy
53b53d3169 fix(editor): appendTransaction 加重入防护 + Enter 行为测试
用户反馈:升级成 taskList 后,空项按 Enter 未退出列表(与斜杠命令创建的
行为不一致)。

单测用 splitListItem 命令验证:升级后的 taskList,有内容项 Enter 分裂出空
taskItem,空项 Enter 正确退出列表(doc 末尾产生 paragraph)。行为与斜杠
命令创建的一致。说明 appendTransaction 产生的节点结构合法。

追加防护:给 appendTransaction 返回的 transaction 打 pluginKey 标记,
重入时跳过,避免 Enter 等操作的文档变化被本插件二次干扰。

happy-dom 无法可靠模拟真实 keydown 链路(派发 KeyboardEvent 时光标映射
不准),Enter 的 bug 需真实浏览器复现。
2026-07-02 14:59:53 +08:00
xfy
873ccb1a80 fix(editor): task list label 内 checkbox 居中
label 默认是 inline 元素,内部 checkbox(inline-block)受父级行高影响会产生
基线间距,导致 checkbox 在 label 盒子内偏上,即便 li 用了 align-items: center
也只对齐了 label 盒子而非 checkbox 本身。

给 label 加 display: flex + align-items: center + line-height: 0,消除行高
撑出的基线空间,让 checkbox 严格居于 label 盒子中心。
2026-07-02 14:42:52 +08:00
xfy
4e96243407 style(editor): 任务列表 checkbox 与文本垂直中线对齐
与前台 input.css 同步,编辑器内 task list 同样改为 align-items: center。

- li: flex-start → center,实现真正的中线对齐
- label: 去掉 margin-top: 0.3em(center 已处理)
- 新增 li > div > p { margin: 0 }:task list 项内段落清掉默认 .6em 0
  上下 margin,否则 margin 撑高 div 导致 checkbox 与文本错位、项间距过大
2026-07-02 14:38:59 +08:00
xfy
17006f8d0c style(post): 任务列表 checkbox 与文本改为垂直中线对齐
flex-start + 固定 margin 是近似居中,在不同字体/行高下会偏。改用
align-items: center 实现真正的中线对齐(checkbox 中线与文本中线重合),
去掉不再需要的 0.35rem 顶部偏移。

单行任务项场景下 center 更合适;多行时 checkbox 会垂直居中于整个文本块。
2026-07-02 14:32:43 +08:00
xfy
250fe653c1 fix(editor): 修复任务列表升级后光标被甩到下一行
appendTransaction 用 replaceWith 整段替换 bulletList→taskList,ProseMirror 的
选区映射无法把原 listItem 内的光标正确映射到新 taskItem 内,默认落到替换
区域之后(下一行)。

显式用 TextSelection.near 把光标设到命中的 taskItem 段落文本末尾:
按命中项在 newItems 中的索引 + 段落文本长度算出目标 pos。

新增光标位置断言(selection.from === 3,即 doc>taskList>taskItem>paragraph 内)。
2026-07-02 14:25:57 +08:00
xfy
62bd281684 fix(admin/posts): 重建结果消息改为独立行,修复悬浮溢出表格的错位
RebuildCacheBar 的「已重建 X 篇文章」结果消息原用 absolute top-full mt-2
悬挂在按钮行下方,但 mt-2(8px) + text-sm(20px) = 28px 大于外层 space-y-6
的 24px 间距,消息底边向下溢出 4px 侵入表格容器上沿;横向 left-0 又使消息
落在右上角按钮组正下方、覆盖状态/日期/操作列,语义位置错位。

将 rebuilding / rebuild_result 状态上提到 PostsPage:
- 结果消息在 header 与表格之间独立成行,进入文档流,吃 space-y-6 正常间距,
  既不撑高 header 触发 items-center 重排(曾用 absolute 规避的旧问题),
  也不脱离流溢进表格(absolute 引入的新问题)。
- RebuildCacheBar 改为接收父级 Signal props,只渲染按钮行本身。
2026-07-02 14:19:06 +08:00
xfy
4a27236859 fix(editor): TaskInputRule 改用 appendTransaction(逐字符输入才生效)
input rule 方案对逐字符输入无效:BulletList 在用户打 - (第2字符)时就抢先
触发,把行变成 bulletList>listItem,之后 [ ] 在 listItem 内无法被任何
input rule 升级。提高 priority 也无济于事——BulletList 在更短文本上抢先。

改用 appendTransaction 监听文档变化:BulletList 触发后,用户在 listItem 里
打出 [ ] / [x] 前缀时,把该 bulletList 整体替换成 taskList,对应 listItem
转成带 checked 的 taskItem 并删除已识别的文本前缀。

- 测试模拟逐字符时序:先 - (applyInputRules 触发 BulletList),再 [ ]
  (appendTransaction 升级),5 个用例覆盖未勾选/已勾选/大写/不误判/星号
- * + - 三种 marker 的 bulletList 都会升级(BulletList 对三者创建同种节点)
2026-07-02 14:16:34 +08:00
xfy
1f8147f323 chore(make): dev 改用 make css 一次性构建 Tailwind 2026-07-02 14:07:57 +08:00
xfy
4a76397af8 feat(editor): 支持 - [ ] 手动输入创建任务列表
Tiptap 官方 TaskItem 的 input rule 只匹配 [ ] 不含 marker,且 StarterKit
的 BulletList input rule /^\s*([-+*])\s$/ 在用户打 - 时就抢先触发,把这行
变成无序列表;后续的 [ ] 沦为字面文本,getMarkdown 序列化时转义成 \[ \],
前台无法渲染成 checkbox。

- 新增 TaskInputRule 扩展,priority 1000 抢在 BulletList(默认 100)之前,
  正则 /^(\s*-\s)\[( |x|X)\]\s$/ 匹配 - [ ] / - [x],
  wrappingInputRule 包裹成 taskList > taskItem(findWrapping 自动补齐两层)
- 仅识别减号 marker(* + 仍走 BulletList),Enter 续行复用 TaskItem 内置
  splitListItem,不重复实现
- 新增 task-input-rule 测试(真实 Editor + happy-dom):覆盖未勾选/已勾选/
  大写 X/不误判普通列表/星号 marker
2026-07-02 13:56:11 +08:00
xfy
865871f45c fix(markdown): 修复任务列表 checkbox 在前台被 sanitizer 剥离
编辑器端已挂 TaskList/TaskItem,但前台渲染走 pulldown-cmark,其输出的
<input type="checkbox"> 不在 sanitizer 白名单内,被 remove_and_keep_content
剥离,导致任务列表在前台只剩裸 <li> 文本。

- sanitizer: 白名单放开 input,但强制 type="checkbox" 属性值校验
  - attrs_to_remove 增加 input.type 值校验,非 checkbox 一律删除属性
  - element_handler 末尾兜底:缺 type 或非 checkbox 的 input 整标签移除
    (void 元素无内容,remove() 不丢正文,封堵 type=image/text 等 XSS 滥用)
- 仅 clean_html 放开,clean_comment_html 保持不放开(评论无需任务列表)
- input.css 补 .md-content 任务列表样式(pulldown-cmark 裸 li>input 结构,
  用 :has() 识别,不支持 :has() 的浏览器降级为普通列表仍可读)
- 新增 sanitizer 5 个测试(白名单/XSS 边界)+ markdown 1 个端到端测试
2026-07-02 13:22:37 +08:00
xfy
a10003c90d feat(theme): 切换按钮点击立即换图标并播放进入动画
将 theme.set 从 450ms 延迟改为立即执行——图标在点击瞬间切换,不再
等待圆形 VT 动画结束。原作者担心重渲染打断 VT 实属多余:VT 伪元素
快照在 __startThemeTransition 内已同步拍好,后续真实 DOM 变化不影响。

- 移除 click_gen 防抖机制与 spawn_local 延迟回调(不再需要)。
- SVG 加 key 属性,theme 变化时 Dioxus 重新挂载 SVG,触发 CSS 进入动画。
- input.css 新增 @keyframes theme-icon-enter(缩放 0.5→1 + 旋转 -60°→0°
  + 淡入,0.3s ease-out),仅在 .theme-toggle svg 上生效。
- prefers-reduced-motion 下禁用该动画,遵循项目既有约定。
2026-07-02 12:00:00 +08:00
xfy
9a4e6c3e6e docs(agents): 校正构建/测试/迁移说明,对齐当前代码库
- 移除不存在的 ./migrate.sh:迁移实际由 src/db/migrate.rs 在启动期自动运行,
  目标库由 db/pool.rs::ensure_database_exists 自举创建;补充新增迁移需在
  MIGRATIONS 数组登记(编译期校验磁盘与数组一致)
- npm → pnpm:4 个 libs 均使用 pnpm-lock.yaml,Makefile 全程 pnpm
- make test / make build 覆盖 4 个 libs(补充 codemirror-editor)
- 补充 restore-webp 兜底步骤的成因(dx 0.7.9 重编码 .webp)
- 补全 make clippy(-D warnings)、make fix、make clean 真实行为
- 模块树补齐 context/router/ssr_cache/sysinfo_sampler 及 *_bridge
- 4 个前端子项目合并为对比表,保留 wasm-bindgen 接线等关键细节
- 保留 Workflow 提交规范
2026-07-02 11:40:47 +08:00
xfy
eacc79ed83 feat(theme): 新增跟随系统主题模式与三态循环切换
Theme 枚举从 {Light, Dark} 扩展为 {Light, Dark, System},新增
ResolvedTheme 表示实际生效明暗。

- System 模式移除 localStorage 持久化,首屏防闪烁脚本因此自动回退到
  prefers-color-scheme 分支,首屏零闪烁。
- use_theme_provider 提供 Theme + ResolvedTheme 双 context;resolved 是
  theme 与 system_dark 的派生 memo。WASM 端注册 matchMedia change 监听,
  系统偏好变化时实时同步 .dark class 与下游(CodeMirror 等)。
- ThemeToggle 由二态 toggle 改为三态循环(Light→Dark→System→Light),
  图标按当前模式切换(太阳/月亮/显示器);仅当实际明暗翻转时才触发
  圆形展开动画,避免 System 与同向 Light/Dark 间无意义动画。
- system.rs CodeMirror 传参改读 resolved,使 System 模式下系统偏好变化
  时编辑器主题自动跟随。
2026-07-02 11:35:28 +08:00
xfy
9a218db478 refactor(assets): 编辑器背景图缩小至 w-24 并贴近右下角
原图无宽度约束,按原始尺寸渲染占满编辑器;现固定 w-24(与评论区
一致)并用 bottom-2 right-2 定位到右下角。一并提交新增的线条小狗
素材图与图标资源。
2026-07-02 11:22:34 +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
fe22d6dad8 feat(editor): tiptap 编辑器有内容时自动调淡背景图透明度
无内容时背景图保持 60% 透明度;当编辑器内有内容(非纯空白)时,
背景图淡出到 10%,与聚焦时行为一致。content signal 由 onUpdate
回调实时维护,输入即触发重渲染,无需额外状态。
2026-07-02 11:11:04 +08:00
xfy
48107d8e18 refactor(assets): 评论区与后台编辑器背景图换用 xiantiaoxiaogou_input_bg 2026-07-02 10:58:52 +08:00
xfy
79425ca678 feat(assets): 添加线条小狗透明背景图
用 rembg (silueta 模型) 对 xiantiaoxiaogou_input.webp 去除粉色背景,
输出无损 WebP (RGBA, 736x587)。alpha 通道经像素抽样验证:
背景全透明, 小狗主体白色填充完整, 边缘平滑无锯齿/光晕/粉色残留。
2026-07-02 10:52:09 +08:00
xfy
fba88639a9 fix(build): dx build 后用源文件还原 webp 产物
dx build 0.7.9 会把 public/ 下的 .webp 重编码成 VP8L 无损静图:
动画帧被完全丢弃(动图变静图),静图因 VP8L 无损反而膨胀 7-8 倍。
这与 Dioxus 文档承诺的「public/ 原样拷贝」不符,疑似 0.7.9 的 bug。

新增 restore-webp target:dx build 完成后遍历所有产物目录
(release/debug),用源 public/ 的同名 .webp 覆盖回去。
仅在 build / build-linux 末尾调用,build-freebsd 不受影响
(用 cargo 直出,部署直接用源 public/)。

上游修复后可移除该 target 及两处调用。
2026-07-01 18:22:45 +08:00
xfy
9cf0e605d4 fix(admin): 修复服务器/数据库状态自动刷新失效 Bug
use_future 旧实现在同步闭包体内捕获 status/loading/error signal,
导致这些 signal 每次 .set() 后都触发 use_future 重建新 loop,
旧 loop 同时继续运行,造成并发 loop 指数增长(请求爆炸),
实际效果等同于自动刷新完全失效或行为异常。

修复:采用 Dioxus 0.7 官方推荐模式——
- use_future 闭包直接返回 async move {},不在同步层读任何 signal
- loop 内每次迭代读 refresh_interval/refresh_ms 的实时值
- 手动模式(interval = 0)改为 200ms yield + continue,
  使切换到自动模式时最多延迟 200ms 生效,避免忙等
- sleep 结束后二次检查 interval,防止 sleep 期间用户切回手动时
  仍然发起一次无意义请求

同时修复 DbStatusTab 和 ServerStatusTab 两处。
2026-07-01 17:09:39 +08:00
xfy
404f85b732 refactor(web): 用 document::Link 注入 favicon,移除手写 index.html
Dioxus 0.7 提供 document::Link 组件,可在 Rust 代码里声明 <link rel="icon">
注入 <head>,与已有的 document::Stylesheet / document::Title 同机制。

之前的方案手写 index.html(复制 dx CLI 默认 shell + 加 favicon 链接),
脆弱:嵌入了 toast 基础设施 JS/CSS,升级 dioxus-cli 后需手动同步。
改用 document::Link 后,favicon 由 SSR 运行时注入,无需维护静态模板。
2026-07-01 17:00:58 +08:00
xfy
5ecaf86879 feat(web): 设置网站 favicon
Dioxus.toml 的 [web.app] 无 favicon 配置项,dx CLI 默认 shell 也不注入
<link rel="icon">。新增 index.html 自定义 shell(复制自 dx 默认模板,含
toast 基础设施),加上 favicon 链接。<title> 留空由 [web.app] title 注入,
避免重复。favicon.ico 放 public/ 根目录随静态资源分发。
2026-07-01 17:00:58 +08:00
xfy
afa6bf892e feat(register): 输入框支持回车键提交表单
注册页 4 个输入框(用户名/邮箱/密码/确认密码)原先 onkeydown 为 None,
无法用回车提交。改为监听 Enter 触发 on_submit,与登录页行为一致。
2026-07-01 17:00:58 +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
7de97d8222 fix(write): 编辑模式回显空白由 effect 竞态导致
编辑文章时编辑器空白。根因:edit_post 由 None→Some 时,回填 effect
(写 content signal)与初始化 effect(读 content signal)在同一 tick
触发,无保证先后顺序。初始化 effect 经常先跑,读到 content()= 跳过
setMarkdown,且 editor_content_set=true 永久阻止重试。

修复:初始化 effect 直接从 edit_post(真值源)取 content_md 回填,不再
依赖 content signal——后者是派生值,与回填 effect 存在竞态。两个 effect
都已在 edit_post 上订阅,回填时读取它没有竞态。
2026-07-01 17:00:58 +08:00
xfy
a4331bd813 feat(build): add FreeBSD x86_64 cross-compile support
方案 A(clang + lld + 本地 sysroot)。rustc 为 freebsd target 链接的系统库
(libc/libexecinfo/libgcc_s/libthr)及 sysinfo 依赖的 freebsd 专有库
(libkvm/libmemstat/libprocstat/libdevstat)需 FreeBSD 运行时,无法纯靠
clang+lld 无 sysroot 完成;故从 FreeBSD 15.1 base.txz 解出 crt 对象 + 系统库
到 .freebsd-sysroot/(gitignored,machine-local)。

- .cargo/config.toml: freebsd target 配置作为注释模板(sysroot 路径机器相关,
  不硬编码进仓库);linker 经 rustflags 的 -C linker=clang 设置,使 Makefile
  能用 CARGO_TARGET_*_RUSTFLAGS 环境变量整体注入。
- Makefile: 新增 freebsd-sysroot(幂等下载+解压 base.txz)与 build-freebsd
  (release server 二进制交叉编译)target。server 用 cargo 直出而非 dx CLI,
  因 dx 对该 target 的支持未经验证。
- .gitignore: 忽略 .freebsd-sysroot/。
- AGENTS.md: 补充构建命令说明。

实测 release 二进制 16MB,stripped ELF for FreeBSD 15.1,动态依赖均为
FreeBSD base 自带库(libc.so.7 libthr.so.3 libkvm.so.7 等)。
2026-07-01 13:40:00 +08:00
xfy
096345d2fe chore: add docker target to Makefile 2026-07-01 10:28:26 +08:00
xfy
23b0256e47 Merge branch 'refactor/unify-tabs-into-filtertabs'
Some checks failed
CI / check (push) Failing after 5m4s
CI / build (push) Has been skipped
统一全局 tab 视觉:删除冗余 Tabs 组件,/admin/system 改用既有 FilterTabs。
- refactor(admin): system.rs tab 栏从 Tabs 切到 FilterTabs(Props 相同,仅改组件名)
- refactor(ui): 删除无调用方的 Tabs 组件(消除 DRY 违反)

结果:/admin/system 与 /admin/comments 的 tab 栏动画/样式完全一致
(平滑滑动指示条 + 选中文字 text-paper-primary)。
2026-06-30 18:19:37 +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
26d37f7271 refactor(admin): use FilterTabs in /admin/system
system.rs 的 tab 栏从 Tabs 改用既有 FilterTabs,视觉与评论页完全一致
(平滑滑动指示条 + 选中文字 text-paper-primary)。Props 形状相同,仅改组件名;
SystemTab::as_str/from_str 桥接与枚举 match 穷举逻辑不变。
2026-06-30 18:05:05 +08:00
xfy
5426458f4a fix(docs): resolve rustdoc broken links and dead_code warnings
Some checks failed
CI / check (push) Failing after 5m18s
CI / build (push) Has been skipped
doc 构建报 8 个 broken_intra_doc_links + dx 构建报 7 个 dead_code 警告。
两者根源相同:被引用的常量/类型/模块要么是 server-only(消费方都在
#[cfg(feature="server")] 块里),要么是跨 cfg 引用(host doc 构建看不到
wasm32-only 类型)。

dead_code:给 sql_console 的 MAX_ROWS/ABSOLUTELY_FORBIDDEN/GuardResult 与
backup 的 BACKUP_DIR/FILENAME_RE/BACKUP_SIGNATURE 加 #[cfg(feature="server")]
gate——这些都是安全护栏常量,消费方全在 server fn 体里,WASM 构建剥掉 fn 体后
常量即成死代码。sysinfo_sampler 的 WASM 桩 read_snapshot 加 allow(dead_code)
(与 codemirror_bridge 既有处理一致)。

rustdoc 链接:跨模块引用改全路径(crate::xxx),跨 cfg 引用(EditorHandle 在
wasm32 mod 内、host doc 构建不可见)转义为纯文本。

验证:cargo doc(0 warning)+ cargo check(default)+ wasm32 check 全部干净。
2026-06-30 17:42:52 +08:00
xfy
95fb8d8698 Merge branch 'feature/admin-database-system'
Some checks failed
CI / check (push) Failing after 5m21s
CI / build (push) Has been skipped
抽取 /admin/system 页面级导航 Tab 公共组件并迁移调用方:
- feat(database): SystemTab::as_str/from_str 字符串桥接 + 单测
- feat(ui): Tabs 组件(与 FilterTabs 并列,页面导航语义,无动画)
- refactor(admin): System 组件改用 Tabs,移除内联 tab 与临时 allow
2026-06-30 14:56:20 +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
ef75631889 feat(database): add SystemTab::as_str/from_str string bridge
为后续用基于 String 的 Tabs 公共组件替换内联 tab 做准备。
as_str/from_str 是纯函数双向映射,带 roundtrip/稳定 key/拒绝未知输入三类单测。
2026-06-30 14:13:54 +08:00
xfy
0b6a076d78 feat(database): use real COUNT(*) for small tables, fall back to estimate for large
row_estimate came from pg_class.reltuples, which is -1 for tables that
have never been ANALYZEd (the common case on a fresh/small DB), so the
admin table list showed -1 for every table — meaningless.

Replace row_estimate with row_count + row_count_estimated:
- total_size < 100MB: SELECT count(*) (real value, cost negligible on
  small tables; identifier quoted via PG escaping to stay injection-safe)
- total_size >= 100MB: fall back to reltuples estimate, flagged so the UI
  can prefix with ~

Frontend now shows real counts (1/2/14...) for small tables and ~N for
large ones; header copy updated to reflect the mixed source.
2026-06-30 13:57:37 +08:00