66 Commits

Author SHA1 Message Date
xfy
93d3858a37 chore(deps): update package-lock.json 2026-06-26 18:15:00 +08:00
xfy
0ff24888ce refactor(theme): use pure CSS View Transitions without WAAPI or style injection
Adopt a fully CSS-driven strategy for the theme transition:
- Use CSS variables (--tt-x, --tt-y, --tt-r) injected onto documentElement
- Define static @keyframes in style.css using these variables
- Key logic: the Dark layer is ALWAYS placed on top (z-index: 2).
  - Light -> Dark: NEW layer is Dark, so NEW expands from circle(0) to cover OLD.
  - Dark -> Light: OLD layer is Dark, so OLD shrinks to circle(0) to reveal NEW.

This completely eliminates reliance on Web Animations API (which had bugs
with clip-path when animation:none is set) and eliminates dynamic <style>
injection (which caused timing gaps and style residue).
The result is perfectly symmetrical and bug-free in both directions.
2026-06-26 18:14:29 +08:00
xfy
76eedebf05 fix(theme): remove animation:none that blocks WAAPI on VT pseudo-elements
Chrome does not allow WAAPI clip-path animations on ::view-transition-new
when animation:none !important is set via CSS. This caused dark→light
transitions to have no visible animation.

Replace with opacity:1 !important to neutralize the UA fade-in/out
animations: the UA animations still 'run' but cannot change opacity
because !important author declarations override CSS animation values.
WAAPI clip-path is no longer blocked.
2026-06-26 18:11:16 +08:00
xfy
0f9c8fb8a3 fix(theme): lock VT pseudo-element opacity/blend via CSS !important
Gray expanding circle was caused by UA default animations (fade-in/out)
and mix-blend-mode: plus-lighter not being fully overridden by WAAPI
opacity animations alone.

Fix: use CSS with !important to hard-lock the VT pseudo-elements:
- animation: none !important — kills UA fade-in/out
- opacity: 1 !important — both layers stay fully opaque
- mix-blend-mode: normal !important — no color addition

WAAPI now only controls clip-path on ::view-transition-new(root),
which doesn't conflict with any CSS property (no CSS clip-path exists).
This cleanly separates concerns: CSS handles static properties,
WAAPI handles the dynamic circle expansion.
2026-06-26 18:08:28 +08:00
xfy
f0ea1a294f refactor(theme): switch VT animation from CSS injection to Web Animations API
Replace dynamic <style> injection (@keyframes + pseudo-element rules)
with document.documentElement.animate({ pseudoElement }) calls.

Previous approach accumulated <style> elements across transitions,
causing subsequent dark→light animations to be invisible. The Web
Animations API:
- Creates per-call Animation objects with no DOM residue
- Has higher composite priority than CSS animations, naturally
  overriding UA default fade-in/out without !important
- Eliminates style element lifecycle management entirely

Static CSS now only sets mix-blend-mode: normal (not animatable via
WAAPI) and the is-theme-transitioning transition suppression.
2026-06-26 18:02:26 +08:00
xfy
b542952619 fix(theme): pre-inject VT styles before startViewTransition
Previous approach injected ::view-transition-new animation in vt.ready,
creating a timing gap where the NEW layer had no clip-path and was fully
visible, making dark→light transitions invisible.

Now:
- Inject <style> with @keyframes + VT pseudo-element rules BEFORE
  calling startViewTransition, so styles are ready when pseudo-elements
  are created.
- Force synchronous getComputedStyle() in VT callback to ensure the
  NEW snapshot captures final background-color, not a mid-transition
  value.
2026-06-26 17:55:45 +08:00
xfy
ba9db2d75a fix(theme): fix dark→light transition animation invisible
The static CSS rule 'animation: none' on ::view-transition-new(root)
caused the new (light) screenshot to be fully visible before the JS-
injected clip-path @keyframes kicked in. This meant the light layer
was already covering the dark layer, making the expanding circle
invisible.

Fix: move all VT pseudo-element styles (animation, mix-blend-mode)
into the dynamically injected <style> block in vt.ready, so they
apply atomically with the clip-path keyframes.

Also:
- Add transition:none !important during VT to prevent body's
  background-color 0.3s transition from producing a stale capture.
- Restore Dioxus theme.set() that was disabled for debugging, so
  the toggle icon and localStorage persist correctly.
- Use !important on injected animation to reliably override any
  UA default VT animations.
2026-06-26 17:50:45 +08:00
xfy
c1781d7831 fix(theme): VT 期间冻结 CSS 过渡,让圆形展开可见
body 有 transition: background-color .3s,VT 在 toggle dark class
后下一帧拍 NEW 快照时背景色才走了 ~5%,新旧快照几乎一样,
圆形展开看不出颜色差。修复:回调里加 vt-freeze 类禁用所有
过渡,让 NEW 快照立即拿到新主题配色;动画结束(finished)后
移除。补 1 个测试覆盖 freeze 生命周期。
2026-06-26 14:36:41 +08:00
xfy
6979bd1010 feat(theme): 圆形展开主题切换动画(View Transitions API)
点击主题按钮时,新主题从按钮位置以圆形向外展开覆盖全屏。
JS 同步 toggle dark class 拍快照 + CSS clip-path circle keyframes;
Rust theme.set 事后对齐状态(use_effect 幂等)。
reduced-motion 与不支持 VT 的浏览器自动降级为瞬切。
2026-06-26 14:06:23 +08:00
xfy
ac1f92d816 refactor(core): 迁移 post-content 到 yggdrasil-core,删除 public/js
逐行 TypeScript 化(逻辑不变),改走 window.__initPostContent 全局入口。
post-content.js 已迁移进 libs/yggdrasil-core/,public/js/ 目录随之删除。
2026-06-26 13:57:48 +08:00
xfy
afabdf4655 chore(core): 搭建 yggdrasil-core 子工程骨架
照搬 lightbox 的 Vite IIFE 工程结构,作为核心 JS 统一归处。
此 commit 仅打通构建管线,业务逻辑后续 task 填入。
同时把 public/yggdrasil-core 加入根 .gitignore。
2026-06-26 13:51:39 +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
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
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
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
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
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
xfy
18ae6a915e refactor(tiptap): type coordinatorRef via Pick<UploadCoordinator> instead of duck typing 2026-06-23 18:34:37 +08:00
xfy
558191e922 refactor(tiptap): type CommandItem.editor as Editor, explicit SlashPopup return type 2026-06-23 18:33:59 +08:00
xfy
073f6936e3 build(tiptap): add tsconfig + typecheck script 2026-06-23 18:32:26 +08:00
xfy
a3896c24a5 refactor(write): replace all js_sys::eval with tiptap_bridge bindings
write.rs changes (Tasks 7-11):
- use_drop: EditorHandle::drop replaces eval destroy + global reset (#1)
- init use_effect: closure-driven bridge.create replaces eval initEditor
  script + 100ms ready polling (#2/#3/#4/#5)
- getMarkdown: read via EditorHandle instead of eval (#8)
- delete upload polling use_future: driven by onUploadEvent closure (#6/#7)
- removeUploadByUploadId: call via EditorHandle instead of eval (#9)

Bridge fixes during integration:
- UploadCountsJs: drop Copy derive (wasm_bindgen types can't be Copy)
- split module: shared types compile on both targets, wasm externs gated
- add WritableExt/Readable/Writable trait imports for .write()/.set()
- use FnMut closures (Signal write/set take &mut self)
- consume_upload_event takes &mut Signal params

Also: remove stale window.__tiptap_content in index.ts source-mode handler.
2026-06-23 11:25:06 +08:00
xfy
92cfda7d5a refactor(tiptap): EditorOptions onReady/onUploadEvent callbacks replace polling 2026-06-23 10:52:30 +08:00
xfy
b09216ef29 refactor(tiptap): UploadCoordinator emit callback replaces window global 2026-06-23 10:52:20 +08:00
xfy
876cb55e91 style(editor): smooth blur/grayscale transition on placeholder image
给 img 加 transition: filter 0.2s ease,blur 出现和消失(上传中→成功/
失败)平滑过渡,而非瞬间切换。transition 写在基础 img 规则上,
确保 is-uploading class 移除时过渡也能触发。
2026-06-22 16:27:02 +08:00
xfy
6e04940edc style(editor): blur uploading placeholder image, keep overlay text sharp
上传中占位符图片加高斯模糊,遮罩文字保持清晰。

- .is-uploading img 加 filter: blur(8px)
- container.is-uploading 加 overflow: hidden 防止 blur 边缘渗出
- overlay 文字(spinner/"上传中…")不受影响:overlay 是 img 的兄弟元素
  而非后代,CSS filter 只作用于目标元素本身,不波及兄弟
2026-06-22 16:24:02 +08:00
xfy
cee4f3f3ef fix(upload): address code review findings
final code review 后的修复:

1. blob 检查收紧(Important): md.contains("blob:") 误伤合法讨论 blob URL
   的代码块/正文,改为检测 markdown 图片语法 ](blob: 才算占位符泄漏
2. 重试错误原地更新(Minor): seen_error_ids 去重逻辑导致重试后再失败时
   顶部提示停留在旧错误文案;改为已存在 id 时原地更新 message
3. destroy 清理 coordinatorRef(Minor): destroy() 调 setUploadCoordinator(null),
   避免 NodeView 指向已销毁的 coordinator(防御性)
4. 删除 NodeView 未用的 editor 字段(Nit): 死代码清理

验证: cargo test (384 passed) / clippy (本次零警告) / dx check / tiptap build 全过
2026-06-22 15:49:23 +08:00
xfy
8ffb4451b1 style(editor): add upload placeholder NodeView styles (overlay/spinner/error) 2026-06-22 15:32:32 +08:00
xfy
34c8b47436 feat(editor): route slash-command image upload through coordinator
- slash-command.ts: SlashCommandOptions 加 onInsertUploading option
- slash-command.ts: 上传图片命令优先走 onInsertUploading(coordinator 占位符+上传),
  退回 uploadFn 直接上传(无占位符)
- index.ts: SlashCommand.configure 注入 onInsertUploading,
  闭包延迟读取 this.coordinator(在 editor 创建后才实例化)
2026-06-22 15:30:39 +08:00
xfy
5d527778a5 feat(editor): wire UploadCoordinator and custom Image in TiptapEditorInstance
- index.ts: 替换 Image.configure 为 UploadImage(自定义扩展+NodeView)
- index.ts: editor 创建后实例化 UploadCoordinator 并注入 NodeView
- index.ts: FileHandler onPaste/onDrop 改走 coordinator.insertUploading
- index.ts: 暴露 removeUploadByUploadId 供 Rust ×关闭调用
- index.ts: destroy 清理 coordinator
- upload-image.ts: 修正 import 来源(Image 来自 extension-image, mergeAttributes 来自 core)
2026-06-22 15:28:40 +08:00
xfy
57bea05aa3 feat(editor): add custom Image extension with upload-state NodeView 2026-06-22 15:25:25 +08:00
xfy
8ce9c58997 feat(editor): maintain window.__tiptap_uploads for Rust polling 2026-06-22 15:24:37 +08:00
xfy
6506931c1d feat(editor): add runUpload and retryUpload to UploadCoordinator 2026-06-22 15:23:57 +08:00
xfy
e06667f792 feat(editor): add UploadCoordinator skeleton with pending map and node removal 2026-06-22 15:22:42 +08:00
xfy
6ff37322b2 feat(editor): add image upload to slash command and build tiptap in make dev
斜杠命令图片上传 + Makefile dev 构建修复

1. 斜杠命令接入图片上传管线
   - slash-command.ts: SlashCommand 扩展改为 Extension.create<Options>,
     新增 onImageUpload 选项;原单一"图片"命令拆成两个:
     · "上传图片"(📤): 仅在 onImageUpload 可用时出现,触发原生文件选择器
       (accept 限定 jpeg/png/gif/webp,与 upload 端点 MIME 一致),
       上传成功后 setImage 插入,失败 console.error(与 FileHandler 一致)
     · "图片链接"(🖼): 保留原 window.prompt URL 方式
   - index.ts: SlashCommand.configure({ onImageUpload }) 透传宿主回调,
     复用 write.rs 已注入的 /api/upload 管线,无需 Rust 端改动

2. Makefile: make dev 不再忽略 tiptap 构建
   - 根因: 原 dev target 只起 tailwindcss watch + dx serve,从不构建
     tiptap 编辑器,导致 dev server 始终 serve 上次 make build 残留的
     旧 editor.js(本次新增的"上传图片"命令在 dev 下永远不生效)
   - 新增 build-editor-incremental target (跳过 npm ci,直接 vite build,~1s)
   - dev 现在依赖 build-editor-incremental,启动前先增量构建编辑器

3. Makefile: clean 不再删 public/tiptap
   - public/tiptap 是跨子项目(npm)构建产物,删除后必须重跑 make build-editor
     (含 npm ci) 才能恢复;原 clean 会删它是陷阱(make clean && make dev
     会导致编辑器直接消失)。现在 clean 只清 Rust 产物和缓存
2026-06-22 14:18:05 +08:00