634 Commits

Author SHA1 Message Date
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
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
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
88558d73d3 fix(markdown): update unsupported-lang test to use ruby
上一个提交为 code runner 加了 Rust 支持,rust 进入语言白名单后,
render_markdown_runnable_marker_on_unsupported_lang_ignored 里的
rust 用例失效(现在 rust 确实会挂 data-runnable)。改用确实不在
白名单的 ruby 作为不支持语言的样本。
2026-07-09 11:50:57 +08:00
xfy
60bfee7f36 feat(build): inject git/rustc/build-time info and print on startup
新增 build.rs 在编译期采集构建元信息(version / git describe / commit
hash / 提交时间 / rustc 版本 / 编译时刻),通过 cargo:rustc-env 注入,
由 src/build_info.rs 用 env! 宏在编译期内联读取,零运行时开销。

启动时(main() tracing 初始化之后)调用 log_build_info() 分 4 行打印
这些字段,方便定位线上跑的是哪个二进制。

设计取舍:
- 只用 std,不引 vergen/git2 等 build-dependencies(git 不可用时降级为
  "unknown",不 fail the build)。
- build.rs 存 Unix 秒,运行时用已有的 chrono 解析回 RFC3339。
- 声明 rerun-if-changed=.git/HEAD,确保切换提交后重新采集。
2026-07-09 11:50:39 +08:00
xfy
0ab3340133 fix(code-runner): allow exec on /tmp tmpfs for compiled languages
Docker tmpfs 挂载默认带 noexec,编译型语言(go/rust)把编译产物
落在 /tmp 后再 exec 会报 permission denied:
  - rust: /tmp/main (run-rust.sh 的 rustc 输出)
  - go:   /tmp/go-build*/b001/exe/main (GOTMPDIR=/tmp 的链接器输出)

解释型语言(python/node)执行根文件系统里的解释器,不在 tmpfs 上,不受影响。

给 /tmp tmpfs 显式加 exec 选项。这是容器执行的固有需求——只要
沙箱支持任何编译型语言,/tmp 就必须可执行。安全模型由只读根文件系统 +
cap_drop=ALL + no-new-privileges + pids/memory/cpu 限制兜底,/tmp 可执行
不引入额外攻击面(容器内本就允许执行自身进程)。
2026-07-09 11:04:27 +08:00
xfy
2f6f8e549d refactor(code-runner): default CODE_RUNNER_LANGUAGES to all-open
CODE_RUNNER_LANGUAGES 未设置时,白名单不再默认为 python,node,
而是放行 LANGUAGES 注册表里的全部语言。该环境变量变为「可选的收窄开关」:
新增语言到注册表即可默认启用,无需再同步运维白名单(避免两边脱节,
如本次 go/rust 就因 .env 未同步白名单导致 is_supported_lang 拒绝)。

- RunnerConfig.languages: Vec<String> → Option<Vec<String>>,
  None 表示不限制
- is_supported_lang: Option 为 None 时直接放行注册表语言
- 测试 is_supported_lang_default_whitelist → is_supported_lang_default_all_open,
  断言 go/rust 在默认(未设白名单)下可用
2026-07-09 11:02:04 +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
c624997c04 feat(code-runner): add Go and Rust language support
为 Code Runner 新增 Go 和 Rust 两种编译型语言,沿用现有 base 镜像分层结构。

Go:
- run_cmd 直接用 go run(单条命令,内部编译+运行)
- 镜像把 GOCACHE/GOTMPDIR/GOPATH 重定向到 /tmp tmpfs
  (只读根文件系统下 /Users/issuser/.cache 不可写)
- default_limits: 1 核 / 256MB / 10s(编译冷启动比解释型慢)

Rust:
- rustc 编译 + 运行是两步,但 docker.rs 注入脚本用 exec 执行 run_cmd,
  exec 替换 shell 进程后 'A && B' 后半段不执行,
  因此镜像内置 /usr/local/bin/run-rust.sh wrapper 封装两步
- default_limits: 1 核 / 512MB / 15s(rustc 内存大、编译慢)

新增镜像与白名单后,默认 CODE_RUNNER_LANGUAGES=python,node,go,rust。
2026-07-09 09:40:32 +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
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
9c3f2242ba feat(stats): add trash count to PostStats for posts page badge
为合并回收站到 /admin/posts 的 tab 设计铺路:PostStats 新增 trash 字段,
统计 deleted_at IS NOT NULL 的文章数,供文章列表页「回收站」tab 角标使用。

- stats.rs: 条件聚合查询增加 trash 列,server/非 server 构造同步补字段
- models/post.rs: PostStats 加 pub trash: i64
- cache.rs: post_stats_cache_roundtrip 测试 fixture 补 trash 字段
2026-07-08 16:40:32 +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
8f6c864f22 chore(tiptap): 移除未使用的 RunCodeOptsJs 重导出
RunCodeOptsJs 只在 mod wasm 内部用作 Closure 类型参数,无外部消费者,
从 pub use 重导出列表移除以消除 unused_imports warning。
2026-07-07 18:34:17 +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
0c1e8b7640 feat(bridge): 新增 make_run_code_closure 桥接编辑器内运行代码
tiptap_bridge.rs:
- EditorOptions extern 追加 set_on_run_code setter (js_name=onRunCode)
- 新增 RunCodeOptsJs 映射(language/source/overridesJson getter)
- 新增 format_run_result + make_run_code_closure
  · start_exec + 500ms 轮询 get_exec_result,30s 上限
  · overridesJson 空串视为 None,畸形 JSON 静默降级
  · 不依赖 server-only 的 languages 模块(前端 extractLang 已提取纯语言名)
- EditorHandle 加 _on_run_code 字段 + new 参数
- pub use 导出 make_run_code_closure + RunCodeOptsJs

write.rs: 跟进 EditorHandle::new 新签名(构造 on_run_code + set_on_run_code)。
2026-07-06 18:11:28 +08:00
xfy
9231e993e4 fix(clippy): 修复两处预存 clippy 错误
- execute.rs: ExecResult/ExecStatus 仅 server 端使用,加 cfg gate
  消除 WASM 目标的 unused_imports
- system.rs: in_scope 外层闭包保留 || execute_for_editor() 包装
  (Closure::new 要求 Fn 可重复调用,直接传闭包会 use-after-move),
  加 #[allow(clippy::redundant_closure)] 并注释原因

cargo clippy 双目标 -- -D warnings 均通过。
2026-07-06 11:42:03 +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
xfy
d3c2e41fd8 fix(codemirror): 编辑器塌缩导致上下背景割裂(height:100% 失效)
上一提交(3c10f72)用 `& { height: 100% }` 让 .cm-editor 撑满容器,
但实测无效——headless chromium 抓取 computed style 显示:

  容器#ed:      h=280px  (min-height)
  .cm-editor:   h=63px   ← 塌缩!只有内容高度
  .cm-gutters:  h=63px
  .cm-content:  h=63px

根因:CSS 百分比高度要求父元素有「明确 height」(不是 min-height)。
容器只有 min-height: 280px,没有 height,`.cm-editor { height: 100% }`
解析为 auto,塌缩到内容高度(3 行 ≈ 63px)。剩余 217px 透出容器背景
(paper-entry/mantle),与 .cm-editor 的 base 色形成上下割裂。

修复:改用 flexbox 而非百分比高度——
- themes.ts:`& { flex: 1 1 0; minHeight: 0 }`(替换 height:100%)
- 容器(system.rs / runner.rs):`display: flex; flex-direction: column`

flex 子项的 `flex: 1` 不依赖父元素 height,能真正填满 min-height 撑开
的空间。headless 验证:.cm-editor 现为 280px,与容器一致,割裂消失。

诊断方法:构建自包含测试 HTML(内联 editor.js),chromium --headless
--dump-dom 抓 getComputedStyle。这是应有的反馈循环,避免了前几轮
凭代码猜测导致的反复误诊。
2026-07-06 00:20:55 +08:00
xfy
b207babdb6 refactor(admin): 统一按钮令牌,消除样式散落
将 7 个 admin 页面中复制粘贴/各自为政的按钮 class 收敛到 ui.rs 令牌:

主操作色系统一为主题绿(bg-paper-accent):
- dashboard/posts「发布文章」、write「发布/更新」+「确认」、runner 语言切换选中态
  改用 BTN_PRIMARY / BTN_PRIMARY_SM(原为深色 bg-paper-primary,现统一为绿)
- system 4 处刷新/执行/创建备份、trash 保存设置、write 发布按钮改用 LoadingButton
  统一 loading 态为 spinner 绝对居中叠加(按钮宽度不变,原 system 用文字切换、
  write 用整体变灰,现全部一致)

描边/图标/行内按钮统一:
- posts 重建、system 刷新列表 → BTN_OUTLINE
- trash 清空回收站 → BTN_DANGER_OUTLINE
- trash 步进 ± 按钮(2 处重复字符串)→ BTN_ICON
- write 关闭 × 按钮(2 处重复)→ BTN_CLOSE_ICON
- system BackupRow 恢复/删除绕过令牌 → BTN_TEXT_AMBER / BTN_TEXT_RED
  (删除色从 red-600 校正为令牌的 red-500,与其他行内删除一致)

消除约 15 处内联 class 字符串重复,system.rs 4 种主操作变体收敛为 1 种。
2026-07-06 00:17:25 +08:00
xfy
545651d304 refactor(ui): 新增 BTN_PRIMARY/BTN_OUTLINE 等按钮令牌与 LoadingButton 组件
消除 admin 页面按钮样式散落问题:
- BTN_PRIMARY / BTN_PRIMARY_SM:主题绿实心胶囊主操作按钮
- BTN_OUTLINE:描边次要按钮(relative 以承载 spinner 叠加)
- BTN_DANGER_OUTLINE:红色描边危险按钮(清空回收站)
- BTN_CLOSE_ICON / BTN_ICON:× 关闭与方形步进图标按钮
- LoadingButton 组件:封装主题绿三态(loading spinner 叠加 / disabled 灰化 / 正常),
  统一全站 loading 态为 posts.rs 的 spinner 叠加模式(按钮宽度不变,避免布局抖动)
2026-07-06 00:17:09 +08:00
xfy
64907401a0 fix(codemirror): 行号区背景与代码区割裂(core base theme 覆盖 catppuccin)
现象:行号列(gutter)背景是 #f5f5f5 浅灰,代码区(content)是 catppuccin
base #eff1f5 浅蓝灰,两者不一致,有明显割裂。

误诊修正:上一提交(e1b312e)以为是容器背景与 CodeMirror 层次扁平,
改了容器为 code-block(crust)。但 CodeMirror 内部 .cm-editor 和
.cm-gutters 都有不透明背景,容器色根本透不出来——所以「没效果」。
本次回退该容器改动。

真因:CodeMirror core 的内置 base theme(@codemirror/view dist index.js:6916)
给 `&light .cm-gutters` 设了 backgroundColor: "#f5f5f5" / dark "#333338",
特异性为 .cm-editor.cm-light .cm-gutters,高于 @catppuccin/codemirror
的 .cm-gutters 覆盖,catppuccin 的 base 背景被 core 默认灰压制。

修复:themes.ts 在 catppuccin extension 之后追加一个 EditorView.theme,
用 backgroundColor: transparent !important 强制 .cm-gutters 透明,
继承 .cm-editor 的 base 色,行号区与代码区完全融合。light/dark 同理。
2026-07-05 23:58:18 +08:00
xfy
e1b312e651 fix(admin): SQL 编辑器 gutter 与 content 背景割裂
现象:行号区和代码输入区背景色看起来不一致,有割裂感。

根因:三层背景色各不相同——容器用 paper-entry(mantle #e6e9ef)、
标题栏用 paper-theme(base #eff1f5)、CodeMirror 自带 base 背景。
容器与编辑器同处 base/mantle 之间,层次扁平,CodeMirror 的 base
浮不出层次,gutter 与 content 的细微差异被放大。

修复:建立 crust > mantle > base 三层明确递进——
- 容器改 paper-code-block(crust #dce0e8,最深)
- 标题栏改 paper-entry(mantle,中)
- CodeMirror 保持自带 base(最浅)

编辑器作为「浅色岛」浮在深色容器上(VS Code / Sublime 的 IDE
经典范式),gutter 与 content 同为 base 完全融合,割裂消失。
2026-07-05 23:10:56 +08:00