194 Commits

Author SHA1 Message Date
xfy
27ed605223 fix(comments): 修复 section.rs / dashboard.rs 括号不匹配导致编译失败
两处同一类错误:match arm 内 `if {} else {}` 表达式后多了一个 `}`,
导致 arm 被提前关闭,触发 "unexpected closing delimiter" 编译错误。

- section.rs:125 — `if !has_any {…}` 块后多余的 `}`,删除使 if-else 合一
- dashboard.rs:119 — 同类问题,另需补 `;` 终止 `let` 语句
2026-07-15 18:12:00 +08:00
xfy
886eeca915 style: 全项目 rsx! 格式规范化(cargo fmt)
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-15 17:49:03 +08:00
xfy
65a7b1226f style: 全项目格式规范化(cargo fmt)+ Docker daemon 断连容错
- 全项目 .rs 统一 cargo fmt 排版(换行/缩进/参数分行/导入排序)
- infra/docker: DOCKER_CLIENT 连接失败不 panic,降级返回 None
  (博客不依赖 Docker,panic=abort 下会导致整个进程崩溃)
- infra/mod: 去除多余空行
- database/mod: 模块声明按字母序重新排序
2026-07-15 17:22:06 +08:00
xfy
0835811487 style(header): 调整 nav 高度 80px → 64px (h-16) 2026-07-15 11:03:27 +08:00
xfy
50e64cd34c fix(code_runner): use_resolved_theme 从 use_effect 闭包提到 cfg 块顶层
dx check 报 4 处 'hook called in a closure: use_resolved_theme':
use_resolved_theme() 是 hook(内部 use_context),被错误地放在 use_effect 的
move || { ... } 闭包内调用。Dioxus 规则要求 hook 只能在组件渲染路径顶层调用。

修复:在两个 #[cfg(target_arch="wasm32")] 块顶层各调用一次 use_resolved_theme()
拿到 Memo<ResolvedTheme>,move 进下方 effect 闭包;闭包内改为调用 memo
(resolved_theme())读值。调用 memo 本身不是 hook(Memo 是 Copy + Fn),

读值的同时建立主题订阅——主题切换时 effect 仍会重跑,行为不变。
范式对齐 ThemeToggle(src/theme.rs:341)的正确用法。

影响 4 处:CodeMirror 挂载/主题同步 effect、xterm 挂载/主题同步 effect。
2026-07-15 10:29:01 +08:00
xfy
25299d266f refactor(admin): 合并 /admin/posts 与 /admin/posts/trash 为单路由 + tab 切换
将「全部文章」与「回收站」从两个独立路由(及各自的分页变体 PostsPage /
PostsTrashPage)合并为单一 /admin/posts 路由,用顶部 tab 在二者间切换。
tab 状态与翻页均改为客户端 signal 驱动(不走路由、不深链),与 system.rs 的
tab 模式一致:admin 内部页,刷新回到「全部文章」第 1 页。

路由瘦身:删除 PostsTrash / PostsTrashPage / PostsPage 三个变体,Router 不再
需要「静态段 trash 优先于 :page」的排序技巧。

主要改动:
- posts.rs: Posts 改为 tab 容器(PostsTab 枚举 + active_tab signal + key 化
  match 渲染),header 文案随 tab 切换;PostsPage 主体下沉为 AllPostsList,
  翻页改 current_page signal + use_paginated 回调式 Pagination。
- posts_trash.rs: PostsTrashPage → PostsTrashPanel(无参,signal 翻页),
  删除入口组件与 PostsTabs import(header/tab 由容器统一提供)。
- PostsTabs: 从路由驱动(use_route)改为 signal 驱动(on_change 回调)。
- 修复 tab 水平对齐 bug:两个 tab 统一 inline-flex items-center 同盒模型 +
  外层容器加 items-center,根除原先「全部文章」(inline 文本) 与「回收站」
  (inline-flex 带角标) 盒模型不一致导致的垂直错位。
- admin_layout.rs: is_posts_route 简化为只匹配 Route::Posts{}。
- RebuildCacheBar 改为完全自持 state(rebuilding + result),结果消息行随组件
  内联渲染,与父组件零耦合。
2026-07-15 10:21:50 +08:00
xfy
51e06cf118 refactor(ui): Pagination 支持可选 on_prev/on_next 回调翻页
翻页要本地化(走客户端 signal 而非路由)时,传入 on_prev/on_next 即渲染
button onclick;不传则保持原路由式 Link,前台/其他分页页面零影响。

为合并 /admin/posts 与 /admin/posts/trash 为单路由 tab 做准备:两个 tab
的列表翻页将改为 signal 驱动。prev_route/next_route 改为带默认值
(Route::Home {} 占位),回调存在时不被读取。
2026-07-15 10:00:40 +08:00
xfy
fade4e180f refactor: 删除死代码 CommentActions 组件
components/comments/actions.rs (CommentActions) 零引用——评论管理
操作(通过/垃圾/删除)已在 pages/admin/comments.rs 用 BTN_SOLID_*
常量实现,actions.rs 是遗留的重复实现。

原计划将它的内联按钮样式提取为 BTN_PILL_* 常量统一,但确认零引用后
直接删除整个文件更干净。trash_comment re-export 补 allow(unused_imports)
(wasm-only import 在 server 构建触发的已知模式)。
2026-07-14 15:09:41 +08:00
xfy
9e57247a2d refactor: 统一 WASM sleep 到 utils::time::sleep_ms
删除 system.rs 的私有 wasm_sleep(与 utils::time::sleep_ms 逐字相同),
6 处调用改用 crate::utils::time::sleep_ms。
ui.rs 的内联 Promise+setTimeout(50ms) 同样替换为 sleep_ms(50)。

消除 3 份 sleep 实现中的 2 份冗余副本,utils::time::sleep_ms 成为全项目
唯一的 sleep 入口(已有 native tokio + WASM 双实现)。
2026-07-14 14:55:34 +08:00
xfy
b39afbb616 refactor: 删除死代码 (delayed_loading.rs / ui.rs EmptyState / 未用 re-export)
- 删除 src/hooks/delayed_loading.rs:未在 hooks/mod.rs 注册,已被
  DelayedSkeleton 组件取代(mod.rs 注释已说明)
- 删除 src/components/ui.rs 的 EmptyState:零调用者,所有实际用法
  都走功能更全的 components/empty_state.rs::EmptyState
- 删除 api/posts/mod.rs 末尾的 render_markdown_enhanced / slug 工具
  re-export:调用方均使用完整路径 crate::api::{markdown,slug}::*,
  这两个跨模块短路径无引用
- 删除 api/comments/mod.rs 的 render_comment_markdown re-export:
  调用方用完整路径 crate::api::comments::markdown::render_comment_markdown

保留 auth.rs::invalidate_user_sessions:有完整文档说明的预留基础设施
(角色变更/封禁场景),非遗留垃圾。
2026-07-14 14:27:31 +08:00
xfy
5ad6ce60cd fix(post): hydration 后点击标题锚点触发整页刷新
WASM 下载完成后点击 TOC/标题 # 锚点会整页刷新且不滚动,而 SSR 阶段(WASM
未就绪)点击正常——这是 Dioxus 0.7 事件委托的副作用:

项目任意位置用 onclick(post_nav_links.rs)即触发 Dioxus 全局 click 委托。
点击 dangerous_inner_html 注入的 <a href="#id"> 时,事件冒泡到根监听器,
handleEvent 发现 <a> 无 Dioxus onclick → 走兜底 handleClickNavigate:
preventDefault 拦掉原生 fragment-scroll,再 browser_open IPC 把 hash 当
外部 URL 整页加载(History::external → location.set_href)。

修复:yggdrasil-core 新增 anchor-click.ts,在 window capture 阶段(早于
Dioxus 的 bubble 委托)拦截同页 hash 锚点点击,stopPropagation 阻止事件
到达 Dioxus,自行 scrollIntoView + replaceState 更新地址栏。中键/修饰键、
外链、目标不存在等情况放行原行为。

81682eb 的 scrollToHash 只在 PostContent 挂载时跑一次,不覆盖点击场景;
本修复与之正交,共同覆盖刷新与点击两条路径。initAnchorClick 幂等。
2026-07-13 18:22:53 +08:00
xfy
ee9d2ecd6e style(ui): 组件圆角类对齐三档梯度(32/16/8)
配合 input.css 的 token 化,把散落在 RSX 组件里的孤悬圆角档位
统一到内容档 16px (rounded-2xl):

消除 24px (rounded-3xl, 4处无规范依据的中间档):
- ADMIN_CARD_CLASS / ADMIN_TABLE_CLASS (ui.rs)
- write.rs 编辑器容器 / write_skeleton.rs 编辑器骨架

消除 12px (rounded-xl, 10处):
- INPUT_CLASS (forms.rs) 与 write 页输入框统一到 16px
- write 页 3 处输入框 + 4 处错误提示框
- admin_layout 退出按钮
- system/admin_skeleton/posts_trash 卡片容器
- write_skeleton 骨架条(模拟输入框形状,跟随真实圆角)

改造后全站只剩 4 种圆角语义: 32px容器 / 16px内容 / 8px微元素 /
胶囊按钮,孤悬档位彻底清除。
2026-07-13 16:21:10 +08:00
xfy
5888f1fc41 feat(system): 备份恢复删除/恢复改用 Popover 确认框替代浏览器 confirm
新增通用 Popover 组件(src/components/ui.rs,与 Tooltip 对称),定位用
position:fixed + MouseEvent::client_coordinates() 视口坐标锚定,逃出表格
overflow-hidden;透明遮罩 z-40 兜底点击外部关闭,组件内 use_effect 注册
全局 keydown 监听 Esc 关闭(use_drop 清理),150ms 淡入缩放动画
(input.css 新增 popover-enter keyframes)。

BackupRow 删除/恢复按钮 onclick 读点击坐标打开对应确认 popover,确认
按钮回调父组件。顺带简化恢复链路:原生 confirm 是阻塞式才需要
pending_restore signal + 确认 use_future 的间接机制,popover 非阻塞后
on_restore 直接 busy + restore_backup + active_task_id,移除 pending_restore
signal 及其 use_future(进度轮询 use_future 不变,仍服务创建备份+恢复)。
2026-07-13 15:08:24 +08:00
xfy
cf14aae17f style(ui): 统一 admin 分页按钮为圆角胶囊样式
admin 分页的上一页/下一页按钮原先用 rounded-sm 方角实心(灰黑底
+ font-mono),与本页其它操作按钮(BTN_OUTLINE 等圆角胶囊)风格
不一致。改为与描边按钮同族的 rounded-full 胶囊,hover 走主题强调
色,禁用态同步圆角。

因三个 admin 列表页(posts / posts_trash / comments)共用
Pagination 组件的 admin variant,此改动一次性统一全部后台分页。
2026-07-13 13:38:50 +08:00
xfy
093940a0d7 fix(theme): skip editor set_theme during VT animation to prevent direct jump
主题切换时,可运行代码块(CodeMirror + xterm)整体瞬切,不受 VT 圆形展开动画
控制——圆形还没展开到代码块,代码块就整体变色了。

根因(真实页面逐帧像素采样 + 事件追踪确认):
ThemeToggle::onclick 先调 __startThemeTransition(同步截 OLD 快照,VT 回调异步),
再调 theme.set(next)。theme.set 触发 Dioxus use_effect → set_theme,这个调用
在 OLD 快照截取后、VT 回调执行前的时间窗内,直接改了实时 DOM 的编辑器背景。
VT 动画播的是伪元素快照(::view-transition-old/new),但实时 DOM 的改动会穿透
伪元素,表现为代码块在圆形展开到达前就整体瞬切。

上一个提交(theme-change 事件)已在 VT 回调内同步 setTheme(进入 NEW 快照),
但 use_effect 的冗余 set_theme 仍在动画期间改实时 DOM,抵消了快照内的正确状态。

修复:CodeMirror + xterm 的 use_effect 在 is-theme-transitioning 期间跳过
set_theme 调用。VT 事件已在快照前同步换肤;动画结束后 is-theme-transitioning
被移除,use_effect 会因 resolved 信号变化重跑,做幂等兜底同步(覆盖非 VT 场景:
系统偏好变化、初始挂载)。

验证(scripts/vt-editor-sweep.mjs,真实 Dioxus 点击路径):
- 修复前:编辑器整体瞬切(t=111ms 全部变 dark,无渐变)
- 修复后:编辑器从右向左被圆形逐步揭示(t=228ms 右侧先 dark,t=313ms 左侧 dark,
  中间点显示圆形边缘抗锯齿色),与页面其他部分同步
2026-07-13 13:33:33 +08:00
xfy
e50941ef24 fix(runner): propagate duration_ms through SSE done event
The SSE streaming path hardcoded '耗时: -' because duration_ms was
never threaded through:

- docker.rs: OutputChunk::Done now carries duration_ms, computed from
  start_container to wait completion via Instant::elapsed
- sse.rs: DonePayload gains duration_ms, serialized into the done event
- runner.rs: WASM done callback formats the real duration instead of '-'

The polling fallback path already had correct duration via ExecResult;
only the SSE path was missing it.
2026-07-13 10:09:15 +08:00
xfy
9191d07200 fix(ui): hide output area until first run; show skeleton while waiting
The output area was always visible (empty terminal container shown on
page load). Now it only appears after the user clicks Run, with three
states:

- Not run: output area hidden entirely (show_output = false)
- Running, no output yet: skeleton screen placeholder
  (running && !has_output)
- Output received: skeleton gone, xterm renders streamed content

Two new signals drive this:
- show_output: set true on Run click, controls output area visibility
- has_output: set true on first stdout/stderr chunk (SSE) or writeAll
  (polling fallback), dismisses the skeleton

xterm mount effect now reads show_output, so it retries after the
container div enters the DOM (when show_output flips true).
2026-07-13 09:49:57 +08:00
xfy
7d153e50f4 feat(ui): CodeRunner switches to SSE + xterm.js streaming output
WASM path: start_exec_stream → EventSource SSE → xterm.js terminal.
stdout/stderr events write to the terminal in real time; done event
sets exit status and closes the connection. Falls back to polling
get_exec_result (writeAll) if EventSource creation fails.

Server path (placeholder, component runs client-side): retains the
original polling logic so both targets compile.

- xterm.js terminal mounted via xterm_bridge (mirrors CodeMirror mount
  pattern: use_effect + TerminalHandle + use_drop)
- run_code split into #[cfg(wasm32)] (SSE) and #[cfg(not)] (polling)
- output area: <pre> replaced with xterm container div
- poll_result / start_sse helpers in WASM-only sse_consumer module
- term_handle declared at component scope (cfg-gated) so the WASM
  run_code closure can capture it

Note: dx check reports use_resolved_theme-in-closure for the new xterm
effects, same as the pre-existing CodeMirror effects (lines 145, 177).
This is an existing project-wide pattern; cargo build on both targets
and cargo clippy pass clean.
2026-07-10 11:58:25 +08:00
xfy
81682eb70e fix(post): 修复 hash 锚点跳转失效
内容异步加载(use_server_future)导致骨架屏阶段标题 DOM 缺失,浏览器原生
fragment-scroll 找不到目标留在顶部;刷新页面也因 SSR 出的是骨架屏而失效。

- 新增 __scrollToHash 全局入口(yggdrasil-core):内容挂载后解码
  location.hash(CJK 百分号编码 → 原始字符)并 scrollIntoView
- PostContent use_effect 末尾复用 invoke_optional_global 调用,标题
  DOM 已就绪时补一次滚动
- 标题加 scroll-margin-top: 6rem,避开 80px sticky header

点击 TOC/标题 hashtag 由原生 anchor + scroll-margin-top 覆盖,无需 JS。
2026-07-10 00:21:08 +08:00
xfy
ecd5b441a0 fix(code-runner): 补齐可运行块上方间距
上一提交只加了 mb(下间距),但上间距仍缺失:PostContent 把每个 HTML
片段包在独立 <div> 里(post_content.rs:162),代码块前的段落成了该 div
的 :last-child,命中 .md-content p:last-child { margin-bottom:0 },
margin 折叠后上方间距归零。

把 mb 改成 my,var() 取同样的 --content-gap-paper(20px),由 CodeRunner
自身的 margin-top 保证上间距,不再依赖上方段落的 margin 是否被 :last-child
规则清零。
2026-07-09 18:03:30 +08:00
xfy
6492e3c902 fix(code-runner): 可运行代码块与正文之间缺少间距
CodeRunner 根 div 没有任何垂直外边距,而普通代码块经 .md-content pre
规则带 margin-bottom: var(--content-gap-paper)(20px)。可运行块根元素是
div 而非 pre,该规则不生效,导致可运行块紧贴其后正文——上方靠前段落
margin 折叠勉强可见,下方完全无间距。

给根 div 加 mb-[var(--content-gap-paper)](沿用组件既有的 var() 任意值
写法),与普通代码块、段落保持一致的 20px 间距。
2026-07-09 17:36:22 +08:00
xfy
f86cb48efe fix(post): 上下篇切换后正文内容不更新
承接 fix(post) 的路由重跑修复:标题/描述更新后,正文仍停留在旧文章。

根因:PostContent 用 use_memo 缓存 split_content_fragments 的结果,但 memo
依赖 ReactiveContext 追踪闭包内读取的 signal 才会重算。content_html 是普通
String prop,读取它不建立订阅——memo 永久缓存首次解析结果。上下篇切换时
content_html prop 已是新文章的 HTML,但 memo 返回旧 fragments,dangerous_inner_html
收到旧 html 字符串,diff 判断属性值未变 → 不生成 setAttribute 编辑 → 正文 DOM
停在旧文章。

标题/描述之所以正常更新:它们直接读 post(来自 use_server_future 的 Resource,
是 signal),不走 memo。

修复:去掉 use_memo,直接在 render 内调用 split_content_fragments(纯函数,
符合渲染纯净性)。每次渲染用当前 content_html 重新解析,diff 收到新 html →
正文 DOM 正确更新。

验证(headless chromium):
- 修复前:/post/rust 点 Next,标题变「写文章页面改为左右布局」,正文仍是
  rust 的「第一步:定义世界」(红灯,复现)
- 修复后:正文随之变为「问题」(绿灯);prev 方向同样通过;首次 SSR 正常
2026-07-09 16:39:41 +08:00
xfy
6a3ac038da refactor(write): 摘要字段移至右侧栏
摘要从左栏(标题下方)移到右栏侧边栏,作为独立分节位于标签与封面图之间,
与链接/标签/封面统一为带边框输入框的分节式布局。骨架屏同步更新。
2026-07-09 14:45:15 +08:00
xfy
19b47b11a4 style(admin): 缩小后台菜单左右边距
aside 容器 p-6 → p-4,菜单整体向两侧收紧
2026-07-09 14:36:57 +08:00
xfy
6e12b33769 fix(write): 彻底修复骨架屏高度不撑满
骨架屏在两条渲染路径都被高度链断裂卡住,内容只占页面 40-50%,
底部大片空白。

根因:高度链在两处父容器断裂,导致骨架屏无法引用到确定高度:
1. 未登录态(admin_layout):骨架屏被包在 div.p-10.animate-pulse,
   该 div 是普通块级元素,无 flex-1 不撑满 main、非 flex 容器使
   骨架屏 flex-1 无效、无 h-full。SSR 初始态走这条。
2. 登录态(write.rs 覆盖层):absolute inset-0 虽撑满,但非 flex
   容器,骨架屏 flex-1 无意义;改 h-full 又遇 height:100% 在
   非显式高度父容器下解析不可靠的经典坑。

修复:统一让两条路径的父容器都成为 flex flex-col,骨架屏根用
flex-1(比 height:100% 可靠,不依赖父显式 height):
- admin_layout 包裹层: p-10.animate-pulse → flex-1.min-h-0.
  flex.flex-col.animate-pulse(去掉对 write 不合适的 p-10,
  非 write 页面 padding 由 main_class 自带)
- write.rs 覆盖层: absolute inset-0 → absolute inset-0 flex flex-col
- write_skeleton 根: h-full → flex-1
2026-07-09 14:29:34 +08:00
xfy
e6ce87b315 fix(write): 修复骨架屏高度不足
骨架屏被放在 write.rs 的 absolute inset-0 覆盖层里,但根容器
用了 flex-1——在非 flex 父层里 flex-1 无效,导致骨架屏不撑满
高度,编辑器区域被压缩,底部留出大片空白。

改 flex-1 为 h-full,让骨架屏根撑满覆盖层高度,内部 flex
flex-col 再正常分配编辑器 flex-1 的高度。
2026-07-09 14:20:58 +08:00
xfy
1716976d91 refactor(write): 骨架屏同步为左右两栏布局
骨架屏原为单列结构(顶部标题+元信息 → 编辑器 → 底栏),
与重构后的页面布局脱节。重写为镜像真实页面:

- 两栏容器:左 flex-1 + 右 w-80 border-l
- 左栏:标题 + 摘要 + 编辑器(flex-1 撑满,含工具栏+正文行)
- 右栏:链接/标签/封面三节(p-6 + border-b 分隔线)
- 底栏:px-6 border-t,圆角按钮占位

这样加载瞬间骨架屏与真实内容对齐,不会出现布局跳变。
2026-07-09 14:11:48 +08:00
xfy
9b39e972e7 style(admin): 收紧后台所有页面左右边距 px-10 → px-6
px-10(40px) 偏宽,收紧到 px-6(24px) 更紧凑。
- admin_layout 的 main_class: 影响 dashboard/comments/posts/
  posts_trash/runner/system 等所有非 write 页面
- write 页面左栏内容区 + 底栏,保持与其它页面一致对齐
2026-07-09 13:52:29 +08:00
xfy
e8fc255b19 fix(code-runner): 文章页运行结果添加最大高度与滚动
输出区原本无高度限制,大量 stdout 会撑开整个卡片/文章页。加 max-h-80
(320px) 与 overflow-auto,超出后垂直滚动。overflow-x-auto 升级为
overflow-auto 以同时支持垂直滚动。与编辑器内 resultArea 的 300px 上限
保持一致。
2026-07-09 13:28:04 +08:00
xfy
d690910efa fix(admin): 消除骨架屏→认证→正常页面的布局闪烁
合并 (true, None) 和 (false, _) 两个分支为统一的 fallback 骨架屏。
旧 (true, None) 分支渲染无侧边栏的全屏居中文字「正在验证身份...」,
与骨架屏 / 正常页面的侧边栏+卡片布局完全不同,在 checked.set(true)
同步触发 re-render 时产生明显的布局跳变。统一后三个状态的布局结构
一致,过渡平滑无闪烁。
2026-07-08 18:23:08 +08:00
xfy
53de550d7c fix(admin): 修复后台骨架屏不可见问题
- admin_layout: 移除 (false, _) 分支中的 DelayedSkeleton 包裹,直接渲染
  骨架屏并附加 animate-pulse。DelayedSkeleton 依赖 use_effect 延迟 200ms
  置 visible=true,但 SSR 不执行 effect,导致服务端 HTML 输出空节点;
  hydration 后 checked 又会立即置 true 退出该分支,骨架屏窗口趋近于零。
  去掉后 SSR HTML 直接包含骨架屏,用户在 WASM 加载 + 身份校验期间可见。

- comments: loading 初始值从 false 改为 true,与 use_paginated hook 保持
  一致。旧值导致首帧 loading()=false,骨架屏条件永远不满足。
2026-07-08 18:20:25 +08:00
xfy
3eaf330673 fix(admin): use flex split layout for write page to fix sticky footer jump
The sticky footer with -mt-12 negative margin caused a jump when
scrolling to bottom: sticky bottom-0 positions against the scroll
container's padding box, but the negative margin pulls the element up
by 48px, so at scroll-end the footer shifts up and clips the editor.

Replace sticky+neg-margin with a proper flex split layout:
- AdminLayout: write route now gets overflow-hidden on the card and a
  padding-free, non-scrolling flex main; other admin pages unchanged.
- write.rs: root becomes flex-col; content area is flex-1 overflow-y-auto
  with the px-10 py-12 padding moved here; footer is a flex-shrink-0
  sibling that sits at the card bottom permanently, never scrolls, never
  jumps.

min-h-0 added on flex children so overflow can shrink below content
height (required for flex + overflow to work).
2026-07-08 18:14:23 +08:00
xfy
25c3172b7c fix(admin): unify write page shell with other admin pages + sticky footer
The write route had a separate shell in AdminLayout (main element acted
as its own rounded card + scroll container), while all other admin pages
used a dedicated rounded-card div wrapping a centered main. This caused
visible margin/spacing differences.

Changes:
- AdminLayout: remove is_write_route shell branching; all admin pages now
  share the same rounded-card div + centered main shell.
- write.rs: drop redundant max-w-7xl mx-auto on root div (main already
  provides it); make bottom action bar sticky bottom-0 with negative
  margins to bleed to card edges and a translucent backdrop so scrolling
  content doesn't bleed through.
2026-07-08 17:42:25 +08:00
xfy
d72495c002 refactor(admin): merge trash into /admin/posts as a URL-driven tab
回收站从独立路由 /admin/trash 合并为 /admin/posts 下的一个 tab,
消除「回收站」导航项,让文章管理(列表 + 回收站 + 自动清理配置)集中在
同一页面,符合「相关功能就近聚合」的组织原则。

路由(router.rs)
- 删除 Route::Trash / Route::TrashPage
- 新增 Route::PostsTrash (/posts/trash) 与 PostsTrashPage (/posts/trash/:page)
- 静态段声明在 :page 之前::page 为 i32 类型,天然排斥 'trash' 文本,
  路由匹配安全且遵循文件内 static-first 约定

导航(admin_layout.rs)
- 移除「回收站」导航项
- 「管理文章」高亮覆盖其下所有子路由(列表/分页/回收站 tab),
  顺带修掉原 PostsPage 分页不高亮的小 bug

页面(posts.rs / posts_trash.rs)
- 新增 PostsTabs:URL 驱动的 tab 栏(Link 切换路由,非本地 signal),
  回收站 tab 带 get_post_stats().trash 数量角标,>0 用主题强调色提醒
- trash.rs → posts_trash.rs:TrashPage 重命名为 PostsTrashPage,
  分页路由改指向 PostsTrash/PostsTrashPage,内容(列表/批量操作/
  清空回收站/AutoPurgeSettings)原样迁移;include_str 测试同步更新
- posts.rs 的 PostsPage 渲染 PostsTabs,列表逻辑不变

无后端契约变更:所有 server function(list/delete/restore/purge/
batch/empty_trash/get_trash_settings)签名不变,Pagination 组件
接收具体 Route 变体,给 trash tab 传 PostsTrash 即可,无需改组件。
2026-07-08 16:52:02 +08:00
xfy
ebeec2a295 fix(comments): resolve reactivity dependency tracking in CommentSection hooks 2026-07-08 15:42:44 +08:00
xfy
a72a167a5f fix(comment): resolve potential SSR hydration mismatch in CommentSection 2026-07-08 15:38:29 +08:00
xfy
74a74e77a8 feat(comment): render form and header persistently to eliminate layout shift 2026-07-08 15:32:24 +08:00
xfy
42548d5c3a style(comment): redesign comment list skeleton to match real list layout 2026-07-08 15:30:32 +08:00
xfy
9e70fd9893 feat(runner): set Vim mode default to enabled in CodeRunner 2026-07-08 14:43:57 +08:00
xfy
cda5a92230 feat(runner): enable Vim mode toggle for CodeRunner sandbox 2026-07-08 14:37:52 +08:00
xfy
3bb67f6673 style(runner): optimize CodeMirror editor height auto-scaling and scrolling limit 2026-07-08 14:32:04 +08:00
xfy
f60bc4beaa fix(runner): fix CodeMirror editing reversion feedback loop 2026-07-08 14:28:45 +08:00
xfy
61c64841f9 refactor(skeletons): 统一骨架屏延迟机制,200ms 内加载不显示
- DelayedSkeleton 从「延迟 pulse 动画」改为「延迟渲染」:
  前 200ms 完全不渲染,超时后才显示并带 pulse 动画
- admin_layout 从 use_delayed_loading hook 改为 DelayedSkeleton 包裹
- CommentListSkeleton 的两处裸用(section.rs / post_detail.rs)
  补上 DelayedSkeleton 包裹
- 移除不再使用的 use_delayed_loading hook 模块
2026-07-08 14:18:08 +08:00
xfy
7d0922c0f9 feat(editor): 可运行代码块在 CodeMirror 挂载前显示骨架屏
SSR 渲染时 CodeMirror 容器为空(JS 编辑器不在服务端拉起),hydration
完成前用户看到一块空白区域。新增 editor_ready signal(声明在 cfg 块外,
SSR/客户端双目标可见),CodeMirror 挂载后置 true、卸载时复位。

容器改为 relative,未就绪时叠加一层绝对定位覆盖骨架屏——5 行宽度递减
(90/70/55/85/40%) 的脉冲占位条,模拟代码缩进语境,配色沿用
paper-code-block / paper-tertiary 语义 token。容器本身始终在 DOM
(不与 CodeMirror 的 getElementById 挂载冲突),editor_ready 置 true
后骨架屏 if 分支消失即从 DOM 移除。

Playwright(release)验证:SSR 即渲染骨架屏(5 占位条,无闪烁),
hydration 后骨架屏消失、CodeMirror 可见。
2026-07-08 11:22:06 +08:00
xfy
b95777e58d fix(editor): 修复 SSR 直接访问时可运行代码块不挂载 CodeMirror
CodeRunner 原用 use_hook(|| format!("code-runner-{}", now_pseudo_unique()))
生成容器 id,后缀依赖 now_millis() 时间戳。Dioxus hydration 不传递
use_hook 状态——SSR 渲染与客户端 hydration 各执行一次 use_hook,时间戳
(以及顺带验证过的 ScopeId)两端不同,导致 CodeMirror create() 在 hydration
时用新 id 去 getElementById,找不到 SSR 渲染的容器(id 是旧值),返回 null
不挂载。路由跳转是纯客户端渲染,use_hook 只跑一次 id 一致,故正常。

改为由父组件传入确定性的 instance_id(PostContent 的片段索引 i,来自纯函数
split_content_fragments 对同一 content_html 的解析,SSR 与 hydration 一致),
container_id 直接派生自该 prop,不进 use_hook。admin 试运行页单实例固定 0。

Playwright(release 构建)验证:SSR 直接访问与路由跳转 .cm-editor 均为 1。
2026-07-08 11:04:40 +08:00
xfy
a37b0ee811 docs: 修复 cargo doc 的两处 broken intra-doc links
- runner.rs: 把含函数调用表达式的 rustdoc 链接 `[crate::codemirror_bridge::get_module().create]`
  改为普通代码片段;get_module 是 WASM 专属符号,server target rustdoc 解析不到
- sql_result_table.rs: 模块级 doc 里裸用 `[SqlResult]` 但该类型来自 server-gated
  模块不在作用域,改为纯代码片段并补全路径
2026-07-07 18:29:54 +08:00
xfy
1db641bca9 refactor(sql-console): SqlConsoleTab 改用 SqlResultTable 组件
删除调用方对 Vec<Vec<Value>> → Vec<Vec<String>> 的预处理(保留类型
信息),结果表格段替换为 <SqlResultTable>,空结果集分支保留。
2026-07-06 11:32:33 +08:00
xfy
59edc7c4e4 feat(sql-console): 表头 sticky + 数据行截断 + 点击展开详情 2026-07-06 11:01:11 +08:00
xfy
b0833eebf7 feat(sql-console): 单元格类型化渲染 (NULL/布尔/数字/文本) 2026-07-06 10:33:46 +08:00
xfy
6d0dd8bb6e feat(sql-console): SqlResultTable 组件骨架 + 模块挂载 2026-07-06 10:30:30 +08:00