258 Commits

Author SHA1 Message Date
xfy
a890257009 feat(admin): Pagination 组件支持页码跳转
当前页码渲染为可编辑输入框(仅回调式翻页且总页数 > 1 时):
聚焦编辑、回车跳转(自动夹取到合法范围,非法输入还原)、失焦回显。
posts / assets / trash 三个 signal 翻页页已接入 on_jump;
路由式翻页(comments、前台)不传 on_jump,页码保持纯文本。
2026-07-27 11:13:26 +08:00
xfy
e33e89c258 fix(assets): 工具栏右侧按钮沉入 FilterTabs 的 mb-6 区域紧贴横幅
工具栏 items-end 对齐的是 flex 项的 margin box:FilterTabs 带 mb-6 使容器
底端低于下划线 24px,右侧按钮组无底 margin 被一并拉到底端,距横幅仅 4px。
按钮组同加 mb-6(移除补偿用的 pb-1)后,按钮底边与 tabs 下划线齐平,
下划线→横幅、按钮→横幅、横幅→网格均为 24px。
2026-07-27 10:01:56 +08:00
xfy
b4bea7db0d fix(assets): 操作横幅上下间距不对称(40px/8px → 24px/24px)
FilterTabs 自带 mb-6 在 flex 工具栏内撑出 24px,再叠横幅 mt-4 形成 40px
空洞,而横幅下方仅网格 mt-2 的 8px。横幅改 mt-4 为 mb-6:上方回归 tabs
标准间距 24px,下方与网格 mt-2 margin 塌陷同为 24px,与 comments/system
页节奏一致。
2026-07-27 09:57:15 +08:00
xfy
55e4712f28 fix(assets): 删除返回「素材不存在」时刷新网格自愈过期数据
行已被其他会话/重建删除时,本页网格仍显示幽灵卡片(success:false 不触发
reload)。refs 为空的业务拒绝即「行已不在 DB」,此时同样 bump reload,
让网格收敛到服务端真实状态;引用中拒绝(refs 非空)不误刷新。
2026-07-27 09:46:46 +08:00
xfy
b98059edcc fix(assets): 引用徽标加 z-10 修复被 blur-img 展示层遮盖
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
灯箱改造(051ca94)把缩略图换成 .blur-img 双层结构后,.blur-img-full
自带 z-index:1,而徽标 span 是 z-index:auto,绘制序被图片压过,
导致「孤儿 / 被 N 篇引用」徽标在 DOM 中存在但不可见。

浏览器实测:修复前 elementsFromPoint 命中 blur-img-full,
修复后命中徽标本身,琥珀色孤儿药丸恢复显示。
2026-07-24 18:31:45 +08:00
xfy
051ca947e3 feat(assets): 素材网格接入灯箱预览
- 缩略图改为与前台正文图一致的 .blur-img 双层结构(?w=20 占位 +
  ?thumb=300x300 展示层 data-src),点击由全局注入的 lightbox.js 接管;
  图集模式(当前页内 ←/→ 切换),灯箱加载原图(data-src 去 query)。
- 数据异步到达/筛选/翻页/刷新后由 use_effect 调 __initLightbox 重新绑定
  (双保险契约同 PostContent:__lightboxSelectors + 直接调用兜底)。
- lightbox.ts 补 data-lb-bound 幂等守卫:Dioxus keyed diff 复用节点时
  重复初始化会叠加 click 监听(点一次开两次);附回归测试。
- invoke_optional_global 提取为 src/utils/js.rs 共享,post_content 复用。

浏览器实测:47 张绑定齐全,原图打开/图集切换/Esc 关闭正常,
筛选重建与重建索引重取后重绑幂等(单次点击仅 append 一次 overlay)。
2026-07-24 17:39:34 +08:00
xfy
45d70e1dc6 refactor(assets): 清理 lint,对齐项目惯例
- api/assets/mod.rs 按 posts/comments 惯例加
  #![allow(clippy::unused_unit, deprecated, unused_imports)]
  (#[server] 宏固有噪音)
- 页面交互导入按 target_arch = wasm32 门控;组件级放行非 WASM 未用绑定
- 合并 check_upload_dimensions 进 upload_dimensions(单函数返回尺寸,
  消除死代码),测试同步更名
2026-07-24 16:44:47 +08:00
xfy
5612f16425 feat(assets): 封面「从素材库选择」联动
- AssetPickerModal:素材网格(最新 60 + 搜索)单击回填封面 URL,
  内嵌「上传新图」(复用 upload_image_file,成功即选中,
  失败留在 modal 内提示),上传期间置位 cover_uploading 拦截保存
- CoverUploader 空态加「素材库」入口(与「输入链接」并列)
2026-07-24 16:32:43 +08:00
xfy
0c47e87d30 feat(assets): 全量重建索引
- rebuild_assets_index:扫 uploads/(跳过点目录)→ upsert assets
  (xmax=0 判别新增;技术字段 IS DISTINCT FROM 才更新,保留 alt,
  幂等重跑 updated=0)→ 删文件已消失的行 → 全表扫 posts 重建 refs
- 磁盘扫描与 header 尺寸读取移入 spawn_blocking
- extract_asset_paths 提升 pub(crate) 供重建复用同一提取逻辑
- 前端「重建索引」按钮(进行中禁点,结果进横幅)
2026-07-24 16:27:02 +08:00
xfy
799455da43 feat(assets): 删除保护、孤儿清理与 alt 编辑
- delete_asset:引用中业务拒绝(Ok(success:false) + 引用文章列表),
  孤儿硬删除(文件 + DB 行级联 refs + 派生缓存)
- purge_orphan_assets:一键清理无引用且超过 7 天保护窗的素材,
  容忍单项文件删除失败,返回数量/释放字节/失败数
- update_asset_alt:管理性 alt 编辑
- invalidate_asset_caches:IMAGE_CACHE 前缀批量失效 + 尺寸缓存失效;
  磁盘派生缓存(哈希命名不可枚举)由既有清理任务回收
- 前端:卡片操作区三态(复制/alt/删除 ↔ alt 编辑 ↔ 确认删除),
  顶栏「清理孤儿」两步确认(带数量与总大小),操作结果横幅
2026-07-24 16:23:00 +08:00
xfy
99b96e7a0c feat(assets): 素材管理只读列表页
- list_assets:引用状态筛选 + filename/alt 搜索 + 时间/大小排序 +
  分页(60/页),一次响应带齐 tabs 计数与可清理孤儿统计
- /admin/assets 网格页:?thumb=300x300 缩略图卡片 + 引用徽标,
  单路由客户端分页(对齐管理文章模式),导航加「素材」入口
2026-07-24 16:15:19 +08:00
xfy
5a10b07bff fix(router): 修复错误冒泡触发的 404 页「返回首页」卡死
PostDetail 对不存在的 slug 抛出 ServerFnError(404) 后,ErrorLayout 的
ErrorBoundary 捕获错误并在 fallback 渲染 NotFound。此时 ErrorBoundary 持有错误,
根据 Dioxus 0.7.9 的渲染逻辑(有错则永远渲染 fallback,不渲染 children/Outlet),
点击 Link 切换路由虽更新了 URL,但页面仍停留在 fallback,表现为「URL 变了页面不变」。

根因:ErrorBoundary 捕获错误后不会自动恢复,必须显式调用 clear_errors() 才能
重新渲染 children。原实现用 Link 导航无法干预点击时机清除错误。

修复:NotFound 与 ErrorLayout 的通用错误 fallback 中,「返回首页」改用 button,
onclick 内先 clear_errors() 清除错误边界,再 navigator().push 导航。
- NotFound 通过 try_consume_context::<ErrorContext>() 获取可能存在的错误边界:
  路由匹配命中的 404 无错误(clear_errors 是 no-op),错误冒泡的 404 才需清除。
- ErrorLayout 的 handle_error 闭包参数本身就是 ErrorContext,clone 后在按钮内使用。
2026-07-24 10:34:18 +08:00
xfy
db74a96de1 style: 格式化 Rust 与 TS 代码 2026-07-23 13:34:27 +08:00
xfy
68e290a9fe feat(write): 新建文章默认发布状态改为直接发布
将 /admin/write 新建模式的默认发布状态从「存为草稿」改为「直接发布」。
编辑模式仍回填文章实际状态,不受影响。
2026-07-23 10:10:51 +08:00
xfy
52e5ee2736 feat(admin): 代码试运行沙箱加 bun 语言按钮
SUPPORTED_LANGS 追加 'bun'(仅 canonical key,不展示别名以免按钮拥挤),
default_source 加 bun 分支——示例用 TypeScript 类型注解(:string / :number)
体现 bun 跑 TS 的语言特性。

aliases(js/ts/rs 等)经 normalize_lang 归一即命中此处某项,按钮无需重复。
2026-07-21 15:24:41 +08:00
xfy
78a8d188d1 fix(admin): 重建结果消息改绝对定位,避免撑高容器顶起按钮
RebuildCacheBar 原用 flex-col 把「已重建 x 篇文章」消息排在按钮下方,
参与文档流。其祖先 header 为 md:items-end(底边固定),消息出现时
新增高度全部转化为按钮上移,「重建内容/重建全部」被顶起、「发布文章」
相对下沉,三者错位。

改为外层 relative + 消息 absolute top-full 脱离文档流:出现/消失都
不影响按钮行高度,布局稳定。双 target 编译通过。
2026-07-16 14:36:33 +08:00
xfy
d1c41e8fc5 fix(wasm): hooks 模块移出 server gate,双端可见
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
mod hooks 原被 cfg(feature = "server") 包裹,WASM 前端无法引用 hooks 中的
代码。移出后 server 与 WASM 两端均能访问 hooks 模块。

同步修正 backup.rs 的 import 排序(cargo fmt 自动调整)
2026-07-16 11:37:13 +08:00
xfy
c6cb401522 refactor(admin): 拆分 system.rs 为 system/ 目录(按 tab 分文件)
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
pages/admin/system.rs 单文件 1792 行装 5 个 tab(数据库状态/服务器状态/SQL控制台/导出/备份恢复),按已有 api/database/ 的对称模式拆成 system/ 目录。

关键前提(已验证):5 个 tab 状态完全独立、互不共享,切换时父组件用 key 强制卸载,零跨文件状态耦合。

拆分结构(与 api/database/mod.rs 全路径风格对称):
- system/mod.rs: SystemTab enum + System 入口 + format_bytes(pub(super),3 tab 共用) + tests
- system/db_status.rs: DbStatusTab
- system/server_status.rs: ServerStatusTab + format_uptime(仅它用)
- system/sql_console.rs: SqlConsoleTab(codemirror_bridge 依赖)
- system/export.rs: ExportTab + urlencode(wasm32)
- system/backup.rs: BackupTab + BackupRow + urlencode_dl(自包含,不再跨文件依赖 export.rs)

路由侧零改动: admin/mod.rs 的 pub mod system + pub use system::System 不变(Rust 自动认 system/mod.rs)。双目标编译 + clippy 严格 + dx check + SystemTab 测试全部通过。
2026-07-15 18:28:39 +08:00
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
ffa0a72e51 feat(admin/posts): tab 栏加平滑滑动指示器动画
PostsTabs 此前用 border-b-2 边框瞬切表示选中态,缺少 tab 切换的滑动动画。
移植 FilterTabs(system/comments 页)的滑动指示器机制:相对定位容器 + 绝对
定位滑块(贴底边盖住外层 border-b),WASM 端测量目标 button 的
offsetLeft/offsetWidth 动态定位,transition-all duration-300 驱动滑块从一个
tab 平滑滑到另一个。

选中态从 border-b-2 边框改为文字颜色区分(text-paper-primary),滑块负责
视觉指示。回收站数量角标保留不变。

与 FilterTabs 视觉一致,但 PostsTabs 因需支持角标故未直接复用 FilterTabs
(后者 items 是纯文本元组)。本地自建 POSTS_TAB_GROUP_ID 计数器给每处实例
唯一 DOM id 前缀。
2026-07-15 10:47:31 +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
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
0e4109358f docs(system): 修复 BackupRowProps 文档里 Popover 的 broken intra-doc link
Some checks failed
CI / check (push) Failing after 11m58s
CI / build (push) Has been skipped
[`Popover`] 简写形式解析失败——Popover 的 use 只在 fn BackupRow 函数体里,
对 BackupRowProps struct 的 doc comment 不可见。补全为全路径
[`Popover`](crate::components::ui::Popover),保留可点击跳转。
2026-07-14 13:39:34 +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
c1530956b0 style(system): 数据导出 tab 卡片宽度与其它 tab 对齐
ExportTab 根容器带了 max-w-2xl 把卡片限宽在 672px,而 DbStatusTab /
ServerStatusTab / SqlConsoleTab / BackupTab 都不限宽填满 max-w-7xl
内容区。去掉 max-w-2xl 让数据导出卡片与其它 tab 宽度一致。内部表单
输入用 w-full,加宽后自然拉伸,无空白。
2026-07-13 14:23:14 +08:00
xfy
2e40836480 fix(system): 备份/恢复任务轮询永不启动导致按钮卡死在 loading
点击「创建备份」后接口返回 task id,但按钮一直转圈、列表不刷新。
根因是 AGENTS.md 踩坑记录里的「Reactive hooks 不追踪普通 props/snapshot」:

进度轮询 use_future 在挂载时把 active_task_id 快照进 _task_id_for_poll
(彼时为 None),use_future 只运行一次即 return;用户点击后 create_backup
返回 task id 并 active_task_id.set(Some(id)),但 future 已结束、永不重跑
→ 轮询不启动 → busy 永远 true。恢复确认 use_future 有同样的快照陷阱
(_pending_for_confirm),导致点「恢复」连确认框都不弹。

改用与 DbStatusTab 自动刷新一致的长生命周期 loop 模式:use_future 只跑
一次,内部 loop 每轮迭代读 active_task_id() / pending_restore() 信号,
空闲时 200ms yield 呼吸。这样信号更新在下一轮迭代即被感知,无需依赖
ReactiveContext 对挂载期快照的订阅。
2026-07-13 13:56:22 +08:00
xfy
8cf34769e5 feat(not_found): commit HTTP 404 status code during SSR 2026-07-09 17:33:19 +08:00
xfy
1b38162ca4 feat(post_detail): propagate NotFound and other load errors to ErrorBoundary 2026-07-09 17:31:49 +08:00
xfy
b5a64f8dcf fix(post): 上下篇切换后可运行代码块消失
承接前两轮修复:路由重跑(79978aa)、正文更新(f86cb48)后,翻页时文章正文
正确切换了,但可运行代码块(CodeRunner)消失——容器 div 还在,CodeMirror
编辑器没挂载。

根因:上下篇切换(/post/rust → /post/go)时 PostContent 组件被复用(仅重渲染,
非重新挂载)。其内部 CodeRunner 用片段索引作 key(runner-{i}),而两篇文章的
代码块索引恰好相同(都是 1/3/5),keyed diff 按相同 key 复用 CodeRunner 实例。
复用的实例保留 use_hook/use_effect 状态:挂载 use_effect 的「防重复 init」守卫
(editor_handle.is_some())阻止 CodeMirror 挂载到新的(已替换的)DOM 容器,
而旧 CodeMirror 还绑定在被销毁的 rust DOM 上。同时 PostContent 自身的 use_effect
(__initPostContent 复制按钮 / 灯箱初始化)也不重跑,新文章交互脚本不初始化。

曾尝试直接给 PostContent 加 key=post.slug,无效:Dioxus 的 key diff
(diff_keyed_children)只在「兄弟节点列表」里生效,对单个非列表元素的 key
变化走 diff_non_keyed 路径、按位置复用,不触发 remount(经 use_hook 探针确认:
翻页后 CodeRunner 函数体执行了但 use_hook 从未重新执行)。

修复:把 PostContent 包进单元素 keyed 列表(for + iter::once + key=slug),
使其进入 keyed diff 路径——slug 变化时旧实例移除、新实例创建,连带 CodeRunner
重新挂载、交互脚本重新初始化。

验证(headless chromium,rust↔go 各含3个代码块):
- 修复前:翻页后容器在、CodeMirror 挂载数=0(红灯)
- 修复后:CodeMirror 挂载数=3,骨架屏清零;prev/next 双向 + SSR 首屏均正常
2026-07-09 17:12:48 +08:00
xfy
1c56fd8ec1 fix(home,tags): 同变体路由分页/切标签后列表不更新
延续上一个 commit(fix(post))发现的同类 bug,统一修复 home 和 tags:

- home: /page/1 → /page/2(同 Route::HomePage 变体)后文章列表不更新
- tags: /tags/a → /tags/b(同 Route::TagDetail 变体)后标签下文章不更新

根因完全一致:use_server_future 闭包内读取的 current_page / tag 是普通 prop
(i32 / String),move 进闭包后成为冻结快照,不建立反应式订阅,同变体导航
复用组件实例时 future 不重跑。

注意 / → /page/2(Route::Home → Route::HomePage,跨变体)不受影响,因为跨
变体会新挂载组件、future 首次运行;只有同变体间的 prop 变化才触发 bug。

修复:闭包内通过 router().current::<Route>() 读取当前 page/tag 建立订阅。
Home(/ 路由,Route::Home 变体无 page 字段)调用 HomePosts 时走兜底分支用
传入的 current_page。

验证(headless chromium,本地设 APP_BASE_URL 绕过 CSRF 403):
- home 修复前:/page/1 点下一页,URL 变 /page/2,首篇仍是 page1 内容(复现)
- home 修复后:首篇随分页变为 page2 内容
- tags 因数据库无多文章标签,无法端到端验证;代码模式与 home 一致且编译通过
2026-07-09 16:22:08 +08:00
xfy
79978aa4a4 fix(post): 修复上/下一篇导航后文章内容不更新
点击文章底部的上一篇/下一篇后,URL 正确变化为 /post/<new-slug>,但页面
内容仍停留在旧文章,只有刷新浏览器才生效。

根因:use_server_future(内部即 use_resource)依赖 ReactiveContext 追踪
闭包内读取的 signal 来决定重跑时机。但 PostDetail 的 slug 是路由宏注入的
普通 String prop,move 进闭包后成为冻结快照,读取它不建立任何反应式订阅。
同一 Route::PostDetail 变体间的导航(/post/a -> /post/b)会复用组件实例、
仅更新 props,而 use_server_future 的 task 只在首次 hook 时创建一次,因此
永远不会重跑。

此前 commit 225bb24 把 slug_signal 镜像 + render 期 set 改成直接读 prop,
消除了反模式,但也丢掉了唯一能触发重跑的订阅源(slug_signal 的读取),
反而引入了这个 bug。

修复:在闭包内通过 dioxus::router::router().current::<Route>() 读取当前
slug。current() 内部调用 subscribe_to_current_context(),在 use_server_future
的 ReactiveContext 中注册订阅,路由变化即触发重跑。render body 保持纯净。

验证(headless chromium,本地需设 APP_BASE_URL 绕过 CSRF 403):
- 修复前:点 Next 后 URL 变 /post/1783580058,标题仍为旧文章(复现 bug)
- 修复后:URL 变,标题随之变为新文章
2026-07-09 15:34:25 +08:00
xfy
6a3ac038da refactor(write): 摘要字段移至右侧栏
摘要从左栏(标题下方)移到右栏侧边栏,作为独立分节位于标签与封面图之间,
与链接/标签/封面统一为带边框输入框的分节式布局。骨架屏同步更新。
2026-07-09 14:45:15 +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
aba2aa2105 style(write): 移除右侧栏背景,回归透明
侧边栏不再用 paper-entry 整栏底色,改为透明(与左栏共用
paper-theme 底),靠 border-l 和节间分隔线划分区域。
同步:输入框改回 paper-entry 底 + 实色边框,封面图拖拽区
也改回 paper-entry,保持与透明侧栏的层次对比。
2026-07-09 14:08:00 +08:00
xfy
abc130eae7 refactor(write): 重新设计右侧栏为分节式轻量布局
原「卡片套卡片」结构(slug+标签包在一个重卡片里、封面图独立卡片)
层级冗余、视觉笨重。改为分节式(section)布局:

- 侧边栏整体用 paper-entry 底色,与左栏 paper-theme 形成层次区分
- 链接/标签/封面图三节用细分隔线 border-b 分隔,而非独立卡片
- 标题改用 uppercase 小标签 + tracking-wide,更专业紧凑
- 输入框改用 paper-theme 底 + 透明边框,聚焦时显主色边框
- 封面图拖拽区底色改 paper-theme,与侧边栏背景对比清晰

移除不再使用的 META_LABEL_CLASS 常量。
2026-07-09 14:03:07 +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
012c21532d refactor(write): 移除「撰写新文章/内容编辑器」页头条
页头条占用了顶部垂直空间且与左侧栏的标题输入框语义重复,
直接移除,让写作区从顶部开始,编辑器获得更多高度。
2026-07-09 13:50:46 +08:00
xfy
7b678481c6 refactor(write): 写文章页改为左右两栏布局,编辑器自适应高度
原单列布局把编辑器挤到折叠线以下、又被元信息卡片压缩(h-[60vh]),
显得太矮。改为左右两栏:

- 左栏(主写作区): 标题 + 摘要 + 编辑器,编辑器改为 flex-1 撑满
  剩余高度(内部 .tiptap-editor/.ProseMirror 已是 height:100%)。
- 右栏(侧边栏, w-80): Slug + 标签 + 封面卡片纵向堆叠,独立滚动。
- 页头条提到两栏之上,底栏(取消/状态/发布)不变,仍横跨两列贴底。

#tiptap-editor 容器 id 保持不变,tiptap_bridge 按 id 挂载不受影响。
所有信号/server function/状态逻辑零改动,纯结构样式调整。
2026-07-09 13:40:37 +08:00
xfy
75767661ac feat(admin): surface Go and Rust on the runner page
SUPPORTED_LANGS 追加 go / rust,并为两者补 default_source 示例源码,
使新增语言在 /admin/runner 试运行沙箱的语言切换按钮中可见、可用。

前端语言集合此前是注册表的手动镜像副本(注释已声明需对齐),
本次保持该约定;runner.rs:15 的注释依然准确。
2026-07-09 10:52:03 +08:00
xfy
e48e992ca1 fix(system): add key to tab content div to fix skeleton delay
Without a key, Dioxus 0.7 VDOM diffing may reuse hook slots from
the previous tab component on tab switch. This caused DelayedSkeleton's
visible signal to retain the old true value, bypassing the 200ms delay.

Adding key based on active_tab forces full unmount/remount on tab switch.
2026-07-09 10:22:29 +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
7c98eee285 fix(admin): remove redundant pt-8 on write page top spacing
After the shell unification, main already provides py-12 (48px) top
padding via the shared admin shell. The write page's own pt-8 (32px)
was a leftover from when it had no outer card, so it stacked to 80px —
much larger than other admin pages. Remove it to match.
2026-07-08 17:46:50 +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
5c37b17cb8 fix(admin): align runner page width with other admin pages
runner.rs was the only admin page using max-w-5xl (1024px) while all
others (system/comments/dashboard/posts/posts_trash/write) use
max-w-7xl (1280px). The page's own comment claimed to align with
dashboard/posts/system but the width class contradicted that.
2026-07-08 17:25:39 +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
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