668 Commits

Author SHA1 Message Date
xfy
df85df3d2e style(empty-state): 配图加圆角,暗色模式降低亮度
- rounded-lg 圆角(= 项目的 --radius-paper 8px)
- dark:brightness-90 滤镜,让彩色配图在暗色主题下退后一些
2026-06-25 13:48:46 +08:00
xfy
cfd2af1fab refactor(empty-state): 内联 SVG 换成 webp 配图
移除手绘的 LineDog 内联 SVG,改用 <img> 引用
public/images/xiaotiaoxiaogou_01.webp 原始插画。
2026-06-25 13:43:23 +08:00
xfy
97b413dd50 docs: 同步 AGENTS.md 与当前代码
修正与代码不符的过时描述:
- server functions 分布补全 comments/ 与 settings.rs(原仅列 auth + posts)
- rate_limit tiers 从 3 个更正为 5 个(strict/upload/image/comment/unknown)
- Rust 测试数 392 → 402

补全 lightbox 子项目文档(新增独立章节,说明其经 Dioxus.toml
全局注入而非 wasm-bindgen 桥接的差异),并在 Build Artifacts
与 make test 描述中补上 lightbox 产物与测试(23 tests)。
2026-06-25 13:39:28 +08:00
xfy
8b581649a6 feat(home): 用「线条小狗」插画替换首页空状态
原空状态仅有一行「暂无文章」文案。新增 EmptyState 组件:
- 内联单色 SVG 配图(双手持相机,取景器内两只小狗),由 webp 重绘为线条画
- 描边用 currentColor 适配明暗主题,屏幕区填 accent-soft 作焦点
- 标题改「还没有文章」,副文案呼应站点定位
- 保留可选 action 字段供后台上下文复用

颜色、圆角、入场动画均沿用项目 Forest 调色板与既有视觉语言。
2026-06-25 13:37:07 +08:00
xfy
4ec8d72b66 feat(db): 启动期自动创建目标数据库
把 scripts/migrate.sh 里 CREATE DATABASE 的逻辑内置进二进制启动流程,
全新部署不再需要手动建库即可首次启动。

- src/db/pool.rs: 新增 ensure_database(),连接 postgres 维护库做
  CREATE DATABASE IF NOT EXISTS 等价逻辑(SELECT EXISTS + CREATE)。
  复用 MIGRATE_STARTUP_TIMEOUT_SECS 窗口应对 DB 起得慢;目标库名经
  is_simple_ident 校验后才拼到 SQL,不合法的名字跳过让正常路径报错。
  tokio_postgres::Config 不 Clone,故逐字段拷贝到新 Config。
- src/main.rs: 在 get_conn_for_startup() 之前调用 ensure_database(),
  失败走统一的 tracing::error! + exit(1) 路径。
- src/db/migrate.rs: 文档注释补明目标库存在性由 ensure_database 上游保证。

新增 2 个 is_simple_ident 单测(392 → 402 tests)。
2026-06-25 11:46:50 +08:00
xfy
21322a4339 fix(card): 提升覆盖层 z-index 使封面图可点击跳转
封面完整图 .blur-img-full 在全局 CSS 有 z-index:1 (input.css:611),
盖在原 z-0 的覆盖层 Link 之上,导致 hover 封面命中 <img> 而非 <a>:
光标非 pointer 且点击不跳转。

将覆盖层提到 z-[2](>封面图 1,<标签 10),命中 <a> 后光标与点击一并解决,
标签 stop_propagation 与 hover 缩放不受影响。
2026-06-25 11:10:43 +08:00
xfy
7242c7d8f5 chore: 将迁移脚本 migrate.sh 移至 scripts/ 目录 2026-06-25 10:33:32 +08:00
xfy
2f861d3e6a chore(lightbox): update package-lock.json
Some checks failed
CI / check (push) Failing after 5m8s
CI / build (push) Has been skipped
2026-06-24 15:57:47 +08:00
xfy
71014aa0de style: 封面图比例统一改为 21:9 超宽
卡片 16:9 仍偏高,统一改为 21:9(超宽荧幕比例),前后台一致:
- input.css: .blur-img.post-card-cover-blur aspect-ratio 16/9 → 21/9
- post_card.rs: 缩略图 thumb=400x225 → 420x180 匹配 21:9
- write.rs: 编辑器封面区 aspect-video → aspect-[21/9]
2026-06-24 15:53:55 +08:00
xfy
5fc8a9aeeb style(card): 卡片封面图比例从 4:3 改为 16:9
- input.css: .blur-img.post-card-cover-blur 的 aspect-ratio 4/3 → 16/9
- post_card.rs: 缩略图请求 thumb=400x300 → 400x225 匹配 16:9
2026-06-24 15:49:53 +08:00
xfy
a9b194f3bf fix(card): 将 blur-img 规则移入 @layer components 修复封面高度
上一次仅提升选择器特异性(.blur-img.post-card-cover-blur)未能生效,
根因是 CSS @layer 级联:未分层(layer 外)规则无条件覆盖分层(layer 内)
规则,与特异性无关。

.blur-img 全局规则原本在 @layer components 之外(line 644),
而卡片规则在 layer 内(line 532),导致卡片 aspect-ratio:4/3 败给
全局 var(--ar),--ar 未定义时高度場陷为 0。

将整个 blur-up 规则块(.blur-img/.blur-img-placeholder/.blur-img-full
及其 is-loaded/.dark 变体)移入 @layer components,使两条 .blur-img*
规则同层竞争,特异性 (0,2,0) > (0,1,0) 才真正生效。
2026-06-24 15:46:24 +08:00
xfy
d48b32b40e fix(card): 修复首页卡片封面图高度場陷
PostCard 的封面容器同时带 .blur-img 和 .post-card-cover-blur 两个类。
.blur-img 的 aspect-ratio: var(--ar) 定义在 .post-card-cover-blur 之后
(同特异性,后定义胜出),而卡片不写 --ar 变量(只有详情页 PostCover
在 SSR 写入),导致 var(--ar) 解析为空 → aspect-ratio 无效 → 高度 0。

用 .blur-img.post-card-cover-blur 双类选择器提升特异性(0,2,0 > 0,1,0),
让固定 4/3 比例可靠覆盖 var(--ar)。
2026-06-24 15:38:53 +08:00
xfy
66d29eaa62 fix(write): 元数据行移至封面上方 + 编辑器撑满修复
布局调整:
- 元数据行(Slug/标签)从封面图下方移到上方,顺序变为
  标题 → 摘要 → 元数据行 → 封面图 → 编辑器

- 滚动主体容器补 flex flex-col:此前 overflow-y-auto 容器内
  子项的 flex-1 失效,导致编辑器只剩 min-h 不撑满。
  加 flex flex-col 后,flex-1 编辑器在窗口够高时正常填充剩余空间。
2026-06-24 15:35:52 +08:00
xfy
198a9b292a refactor(write): 封面区空态矮横条 + 主体可滚动
优化编辑器可用高度,避免封面区过度挤压:

A. 封面区空态改为矮横条(h-14,图标+提示+URL链横向排列),
   不再占用 16:9 的大块空间;有图/上传中时才展开 aspect-video。

B. 元信息区+编辑器+错误提示包裹进 overflow-y-auto 滚动容器,
   底部操作栏固定吸底(border-t 分隔)。
   编辑器区从纯 flex-1 改为 min-h-[400px],保证最小编辑空间,
   窗口过矮时不被元信息挤压到不可用。
2026-06-24 15:23:32 +08:00
xfy
8971fbe1a2 feat(write): 支持封面图上传(拖拽/点击/粘贴)
将编辑页封面图从纯 URL 输入框升级为完整的上传控件:
- 16:9 拖拽区,支持拖拽文件、点击选择、剪贴板粘贴三种上传入口
- 完整交互状态:空态/上传中(骨架占位)/预览/错误提示
- 保留外链 URL 输入(内联折叠),覆盖图床/CDN 场景
- 上传复用现有 POST /api/upload,服务端零改动

tiptap_bridge 提取 upload_image_file 通用上传函数,
make_upload_upload 改为薄包装,消除 fetch 逻辑重复。

Cargo.toml 补 web-sys feature(ClipboardEvent/DataTransfer/FileList/
HtmlInputElement),供粘贴/拖拽读取文件。
2026-06-24 15:09:47 +08:00
xfy
ff2694c5a3 feat(image): make size/pixel limits configurable, double defaults
Some checks failed
CI / check (push) Failing after 6m23s
CI / build (push) Has been skipped
将 MAX_IMAGE_DIMENSION / MAX_IMAGE_PIXELS 从编译期 const 改为运行时
LazyLock(启动读环境变量),默认值 x2。

- 默认值:单边 4096→8192,总像素 25M→50M
- clamp 策略:只设下限防危险小值(DIMENSION≥512,PIXELS≥1M),
  超下限 clamp + warn;无上限,完全信任运维
- 内存影响:默认 50M 像素对应 ~200MB/图解码缓冲(max_alloc)
- 模式复用 WEBP_CONFIG:env→parse→(val,clamped)→clamp→warn→info
- 所有引用点解引用(*MAX_...),含 upload.rs/webp.rs 跨文件
- check_upload_dimensions 超限文案改为动态读取上限,不再硬编码数值
- 测试改用 *MAX_IMAGE_DIMENSION+1,数值无关

.env.example 与 AGENTS.md 同步更新环境变量说明。
2026-06-24 13:54:36 +08:00
xfy
933c7a8c93 docs(env): document hardcoded image upload size limits
本次上传尺寸统一为硬编码 const(非环境变量),但 .env.example 已有
记录硬编码默认值的体例(如图片缓存头)。补一段说明上限数值与超限行为,
便于部署者知晓 4096/25M 的存在及调整方式。
2026-06-24 13:26:11 +08:00
xfy
0777fd469a fix(upload): unify image size limit across all formats
上传入口此前对超大图片行为不一致:JPEG/PNG 因 decode limits 失败后
静默存原图,WebP 报"损坏",GIF 完全不查。统一为 header 阶段硬拒绝。

- image.rs: 新增 check_upload_dimensions,按 MIME 只读 header 拿尺寸
  (WebP 走 zenwebp header,JPEG/PNG/GIF 走 image into_dimensions),
  超 MAX_IMAGE_DIMENSION/MAX_IMAGE_PIXELS 返回友好提示
- upload.rs: magic bytes 校验后插入统一尺寸校验,三种格式同路径
- upload.rs: JPEG/PNG 转码 fallback 的 Err(_) 改为 Err(e) 带原因日志
  (超限已在 header 阶段拦截,此处仅剩真损坏兜底)

读取侧 serve_image 不动;webp::decode 内的像素校验保留(读取侧仍需)。
2026-06-24 13:25:23 +08:00
xfy
eed0b24ec3 Merge branch 'refactor/lightbox-modernize'
Some checks failed
CI / check (push) Failing after 5m14s
CI / build (push) Has been skipped
灯箱 index.ts 现代化重构。先补 happy-dom 行为回归测试(11 个黑盒用例覆盖
循环闭包捕获、打开/关闭、图集循环边界、单张不切换),再做重构、用 23 个
测试全绿作证行为不变:
- var → const/let
- for+var+IIFE 包装 → for..of/forEach + const
- 字符串拼接 → 模板字面量
- 拆外层 IIFE(ES module 作用域已封装)
2026-06-24 13:17:18 +08:00
xfy
25eacc1c3c refactor(lightbox): modernize to const/let, template literals, drop IIFE
纯现代化重构,行为不变(23 个回归测试全绿作证):
- var → const/let(state、gotoIndex 的 newIndex 用 let,其余 const)
- 循环 for+var+IIFE包装 → for..of/forEach + const(天然捕获迭代变量,
  消除旧 var 循环闭包必须立即执行函数固定变量的写法)
- 字符串拼接 → 模板字面量(counter 文本、transform、width/height)
- 事件处理 function() → 箭头函数(无 this 依赖场景)
- 拆掉外层 (function(){'use strict'; ... })():ES module 作用域已封装,
  IIFE 是历史遗留;辅助函数仍用 function 声明(openLightbox↔closeLightbox
  互递归依赖提升)

几何纯函数(fitCentered/transformFor/originalUrl)已在 geometry.ts,
本文件只剩 DOM 胶水 + state 单例。
2026-06-24 11:44:06 +08:00
xfy
aadfed2595 test(lightbox): add happy-dom behavior regression tests for refactor
为现代化重构(var→const、拆 IIFE、for→for..of)补行为回归防线。
黑盒驱动 window.__initLightbox,覆盖高风险路径:
- 循环闭包捕获 idx(点第 1/2/3 张 counter 显示正确序号)
- 点击打开(overlay 创建、origSrc 去 query、caption、单张 counter 隐藏)
- 关闭(Esc / 点背景;点图片本身不关)
- 图集 gotoIndex 循环边界(首末衔接 + originNode 更新后焦点归还)
- 单张图不参与切换

happy-dom 20.10.6 作 devDep,vitest 默认环境改 happy-dom;
geometry.test.ts 用 // @vitest-environment node 指令保持纯函数在 node 跑。

23 tests (12 geometry + 11 behavior) 全绿。
2026-06-24 11:40:36 +08:00
xfy
ffaf191398 Merge branch 'feat/lightbox-typescript-build'
灯箱 TypeScript 化与独立构建:将 public/js/lightbox.js (540 行 JS) 迁移为
libs/lightbox/ 独立 TS 工程,镜像 libs/tiptap-editor/ 结构。Vite 构建到
public/lightbox/,经 Dioxus.toml 全局注入,Rust 端去 include_str! 改用
__lightboxSelectors 配置 + IIFE 自启动双保险契约。

- geometry.ts (纯函数 + 12 Vitest 用例) / index.ts (DOM 胶水 + state)
- .lightbox-* CSS 搬入 lightbox.css;.blur-img* 留 input.css (列表页依赖)
- 依赖: typescript 6.0.3 / vite 8.1.0 / vitest 4.1.9 (npm latest)
- 修复关闭灯箱时 focus() 触发的页面滚动 (preventScroll)
2026-06-24 11:31:48 +08:00
xfy
507d271a2b fix(lightbox): prevent page scroll on close via focus({ preventScroll: true })
关闭灯箱时 removeOverlay() 调用 .focus() 归还焦点给原图,浏览器的 focus()
默认会 scrollIntoView,导致页面自动滚动把原图完整纳入视口。用户向下滚动后
只点露出的下半截图、再 Esc 关闭时,页面会向上跳到整图可见——非预期行为。

focus({ preventScroll: true }) 抑制该滚动(Chrome68+/FF68+/Safari15.4+ 支持),
焦点归还的无障碍语义不变。
2026-06-24 11:27:15 +08:00
xfy
05e2989278 build(lightbox): use npm install (npm 11 ci rejects cross-platform rolldawn optional deps) 2026-06-24 11:18:40 +08:00
xfy
022b63aedc refactor(lightbox): remove legacy public/js/lightbox.js (migrated to libs/lightbox) 2026-06-24 11:07:46 +08:00
xfy
0301185a17 refactor(post-content): drop lightbox include_str!, use config-driven init 2026-06-24 11:07:24 +08:00
xfy
adf9e9cf07 build: add build-lightbox targets, wire into build/build-linux/dev/test 2026-06-24 11:06:03 +08:00
xfy
3284b03819 build: register lightbox assets in Dioxus.toml, gitignore public/lightbox 2026-06-24 11:04:29 +08:00
xfy
b4ef906141 refactor(lightbox): move .lightbox-* CSS into lightbox project, wire import 2026-06-24 11:02:44 +08:00
xfy
5f57d375ea feat(lightbox): migrate DOM logic to TypeScript index.ts with self-start contract 2026-06-24 10:57:23 +08:00
xfy
44fc641aaa feat(lightbox): extract geometry pure fns with vitest coverage 2026-06-24 10:52:48 +08:00
xfy
c5af4457e2 feat(lightbox): scaffold libs/lightbox TS project (vite + tsc + vitest) 2026-06-24 10:49:58 +08:00
xfy
1cc09e4cbc fix(bridge): use unchecked_into for TiptapEditorModule (not dyn_into)
Some checks failed
CI / check (push) Failing after 4m44s
CI / build (push) Has been skipped
dyn_into runs 'instanceof TiptapEditorModule', but the IIFE TiptapEditor
is a plain object literal — not an instance of wasm-bindgen's registered
constructor — so the check always fails with a panic.

unchecked_into does only the compile-time type annotation; Reflect::get
already guarantees we have the right object.
2026-06-24 10:38:33 +08:00
xfy
410d594ac0 fix(bridge): read window.TiptapEditor via Reflect.get, not function call
The extern 'fn get_module() -> TiptapEditorModule' was compiled by
wasm-bindgen to 'window.TiptapEditor()' — a function call. But
TiptapEditor is the IIFE module object, not a function, so this threw
'window.TiptapEditor is not a function'.

Replace with js_sys::Reflect::get(&window, "TiptapEditor") +
dyn_into, which reads the property without invoking it.

This bug predated the Vite 8 upgrade but only surfaced after the
EditorOptions fix let initialization progress further.
2026-06-24 10:35:11 +08:00
xfy
fb31ecb5ae fix(tiptap): expose EditorOptions as runtime class for wasm-bindgen
EditorOptions was a TS interface (erased at runtime), but the Rust
wasm-bindgen extern uses #[wasm_bindgen(constructor)] which generates
'new EditorOptions()' — requiring a global constructor.

Vite 5/Rollup accidentally leaked the name into IIFE global scope,
masking this. Vite 8/Rolldown strictly erases interfaces, exposing it
as 'EditorOptions is not defined'.

Fix: make EditorOptions a class (retains runtime constructor) and mount
it on window, since the IIFE bundle name is taken by TiptapEditor.
2026-06-24 10:27:07 +08:00
xfy
368a651fa6 build: make test runs both cargo test and vitest 2026-06-24 10:14:20 +08:00
xfy
c0af99736d docs: update AGENTS.md for vite 8 build, vitest tests, and tiptap toolchain 2026-06-24 10:12:16 +08:00
xfy
08c2539dfc test(tiptap): add isValidUrl tests covering scheme validation edge cases 2026-06-24 10:10:39 +08:00
xfy
90cff2d0cc test(tiptap): add UploadImageNodeView tests for rendering/update/callbacks/destroy 2026-06-24 10:09:36 +08:00
xfy
f7042d3ab4 test(tiptap): add UploadCoordinator tests for counts/lifecycle/emit/idempotency 2026-06-24 10:07:43 +08:00
xfy
8ff051689f refactor(tiptap): export isValidUrl and UploadImageNodeView for testability 2026-06-24 10:04:59 +08:00
xfy
19532670d3 build(tiptap): add vitest config with happy-dom environment 2026-06-24 10:04:23 +08:00
xfy
21c0359f5e build(tiptap): upgrade to vite 8/vitest 4/typescript 6/tiptap 3.27, migrate to rolldownOptions 2026-06-24 10:03:47 +08:00
xfy
939e0de171 build(tiptap): run tsc --noEmit before vite build 2026-06-23 18:45:17 +08:00
xfy
6ffcd55c94 fix(tiptap): correct link command order + validate URL scheme in slash commands 2026-06-23 18:42:57 +08:00
xfy
e580c3ab18 refactor(tiptap): narrow updateNodeAttrs type, extract findImageNodeByUploadId helper 2026-06-23 18:42:05 +08:00
xfy
d099db4140 refactor(tiptap): move coordinator to editor.storage, drop dead Markdown html option 2026-06-23 18:39:38 +08:00
xfy
6b44a9c036 fix(tiptap): clean up pending upload on NodeView.destroy to prevent blob leak 2026-06-23 18:36:47 +08:00
xfy
2e239908a4 fix(tiptap): revoke blob on node destroy + maintain upload counts internally 2026-06-23 18:36:07 +08:00
xfy
71df6abe5d refactor(tiptap): drop redundant single-arg mergeAttributes call 2026-06-23 18:35:05 +08:00