70edc5e46c
feat(frontend): mermaid 流程图懒加载渲染
...
mermaid 无官方 SSR 支持,采用客户端 IntersectionObserver 懒加载:服务端
markdown.rs 只产出普通 <pre><code class="language-mermaid"> 代码块,
前端在块进视口时动态 import 独立 bundle(~1MB)渲染成 SVG。
新增 libs/mermaid-renderer/:mermaid 11.16 IIFE bundle,输出 public/mermaid/
yggdrasil-core/mermaid.ts:扫描 language-mermaid 块,视口可见时动态 import
/mermaid/mermaid.js(单例缓存),mermaid.initialize 适配 light/dark 主题,
securityLevel=strict;渲染失败加 .mermaid-error class 保留源码;幂等守卫
post-content.ts:initCopyButtons 跳过 language-mermaid 块(图无需 copy)
post_content.rs:use_effect 调 __initMermaid('.post-content', theme),
读 use_resolved_theme() 传主题并建立订阅
新增 6 个 mermaid 单测(扫描/主题/幂等/非mermaid/未命中/错误回退)。
550 Rust + 28 前端测试全过,双 target 编译通过。
2026-07-16 14:22:43 +08:00
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
886eeca915
style: 全项目 rsx! 格式规范化(cargo fmt)
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-15 17:49:03 +08:00
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
0835811487
style(header): 调整 nav 高度 80px → 64px (h-16)
2026-07-15 11:03:27 +08:00
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
6a3ac038da
refactor(write): 摘要字段移至右侧栏
...
摘要从左栏(标题下方)移到右栏侧边栏,作为独立分节位于标签与封面图之间,
与链接/标签/封面统一为带边框输入框的分节式布局。骨架屏同步更新。
2026-07-09 14:45:15 +08:00
19b47b11a4
style(admin): 缩小后台菜单左右边距
...
aside 容器 p-6 → p-4,菜单整体向两侧收紧
2026-07-09 14:36:57 +08:00
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
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
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
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
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
d690910efa
fix(admin): 消除骨架屏→认证→正常页面的布局闪烁
...
合并 (true, None) 和 (false, _) 两个分支为统一的 fallback 骨架屏。
旧 (true, None) 分支渲染无侧边栏的全屏居中文字「正在验证身份...」,
与骨架屏 / 正常页面的侧边栏+卡片布局完全不同,在 checked.set(true)
同步触发 re-render 时产生明显的布局跳变。统一后三个状态的布局结构
一致,过渡平滑无闪烁。
2026-07-08 18:23:08 +08:00
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
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
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
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
ebeec2a295
fix(comments): resolve reactivity dependency tracking in CommentSection hooks
2026-07-08 15:42:44 +08:00
a72a167a5f
fix(comment): resolve potential SSR hydration mismatch in CommentSection
2026-07-08 15:38:29 +08:00
74a74e77a8
feat(comment): render form and header persistently to eliminate layout shift
2026-07-08 15:32:24 +08:00
42548d5c3a
style(comment): redesign comment list skeleton to match real list layout
2026-07-08 15:30:32 +08:00
9e70fd9893
feat(runner): set Vim mode default to enabled in CodeRunner
2026-07-08 14:43:57 +08:00
cda5a92230
feat(runner): enable Vim mode toggle for CodeRunner sandbox
2026-07-08 14:37:52 +08:00
3bb67f6673
style(runner): optimize CodeMirror editor height auto-scaling and scrolling limit
2026-07-08 14:32:04 +08:00
f60bc4beaa
fix(runner): fix CodeMirror editing reversion feedback loop
2026-07-08 14:28:45 +08:00
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
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
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
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
1db641bca9
refactor(sql-console): SqlConsoleTab 改用 SqlResultTable 组件
...
删除调用方对 Vec<Vec<Value>> → Vec<Vec<String>> 的预处理(保留类型
信息),结果表格段替换为 <SqlResultTable>,空结果集分支保留。
2026-07-06 11:32:33 +08:00
59edc7c4e4
feat(sql-console): 表头 sticky + 数据行截断 + 点击展开详情
2026-07-06 11:01:11 +08:00
b0833eebf7
feat(sql-console): 单元格类型化渲染 (NULL/布尔/数字/文本)
2026-07-06 10:33:46 +08:00