Compare commits

...

24 Commits

Author SHA1 Message Date
xfy
8ec4ecd310 chore: migrate JS subprojects from npm to pnpm
Some checks failed
CI / check (push) Failing after 4m41s
CI / build (push) Has been skipped
Switch tiptap-editor, lightbox, and yggdrasil-core from npm to pnpm.
Replace package-lock.json with pnpm-lock.yaml and update Makefile accordingly.
2026-06-29 10:35:04 +08:00
xfy
cb73a9b979 chore: fix cargo clippy warnings (unused import and false positive unused_mut) 2026-06-29 10:14:27 +08:00
xfy
178870427c feat(ui): refactor EmptyState to accept dynamic string props and add empty state to archives page 2026-06-29 10:11:00 +08:00
xfy
824ad5a3d3 chore(Makefile): remove build hints and start target
Build instructions have been moved to AGENTS.md.
The verbose echo messages and the standalone start target
are no longer needed.
2026-06-29 09:59:14 +08:00
xfy
7615def3d1 fix: add required [web.resource.dev] for dioxus 0.7.9
dioxus-cli 0.7.9 requires a `dev` sub-table under [web.resource].
Added [web.resource.dev] with the same style/script arrays as the
production [web.resource] section.
2026-06-29 09:56:07 +08:00
xfy
18db852f30 fix(build): correct asset path resolution for local release build and remove duplicated resources 2026-06-26 18:35:48 +08:00
xfy
225c477f8c chore(theme): remove debug prints 2026-06-26 18:27:27 +08:00
xfy
82a8c511fc fix(theme): remove dark class from AppRouter div 2026-06-26 18:24:00 +08:00
xfy
230ec60f24 fix(theme): remove transition from body to fix VT snapshots
The CSS transition on the body's background-color (0.3s ease) was
causing the browser's View Transitions to capture a stale snapshot
during the Dark -> Light change. Even though the .dark class was
removed synchronously and we tried to disable transitions via an
!important override class, the interaction between Tailwind classes
and the raw CSS transition on body still caused the root VT snapshot
to render as Dark when it should have been Light.

By removing the transition from body entirely (which is redundant
anyway since we're using View Transitions for theme changes), the
body jumps to its target color instantly, guaranteeing the NEW
snapshot accurately reflects the final Light theme, eliminating
the 'ghost circle' or 'circle outline' bug.
2026-06-26 18:21:13 +08:00
xfy
065302544d fix(theme): always expand NEW layer and explicitly set html background
When turning Dark off (Dark -> Light transition), the NEW layer is Light.
Previously we tried shrinking the OLD layer (Dark) over it, but if the
root snapshot captured a transparent html, revealing the transparent layer
produced no visual change.

Now we:
1. Always keep the NEW layer on top (z-index: 2).
2. Always animate the NEW layer expanding from 0 to max radius.
3. Explicitly set background-color on html in input.css to ensure the
   VT root snapshots are fully opaque, avoiding transparent captures that
   make expansions invisible.
4. Add background: var(--color-paper-theme) directly to ::view-transition-new
   just to be absolutely certain it's painted opaque.
2026-06-26 18:17:13 +08:00
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
1e78c7f0f9 fix(doc): 修复 /doc 路由与 Dioxus 静态托管冲突的 panic
Dioxus 在 dev 用 nest_service("/doc", ServeDir) 托管 public/doc/ 目录,
手动在 static_routes 注册 /doc 会在 merge 时触发路由冲突 panic。

根因(dioxus-server serve_dir_cached):dev 模式对 public/ 下的目录用
nest_service 挂整个 ServeDir,注册了 /doc;我在 static_routes 加的
精确 /doc 路由与之同名冲突。

修复:
- main.rs: 移除手动注册的 /doc 与 /doc/ 重定向路由,交给 Dioxus 托管。
  文档深层路径 /doc/yggdrasil/* 由 ServeDir 直接服务。
- Makefile: doc 目标拷贝后额外生成 public/doc/index.html 重定向页,
  ServeDir 访问目录根时返回它,经 meta refresh + JS 跳转到
  yggdrasil/index.html,让裸路径 /doc 也能直达文档。
- build/clean 同步集成 make doc 与清理 public/doc。
2026-06-26 14:27:21 +08:00
xfy
30a861f910 docs(agents): 新增 yggdrasil-core 子工程章节
与 lightbox/tiptap-editor 并列,记录用途、构建、注入方式与
主题展开动画的 View Transitions 实现。同步更新 make test 注释
与 Build Artifacts 列表。
2026-06-26 14:09:46 +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
a0067c73d6 chore(core): yggdrasil-core 接入构建管线与全局注入
Dioxus.toml 追加 yggdrasil-core 的 css/script;Makefile 加
build-core / build-core-incremental,接入 build/build-linux/dev/test。
2026-06-26 13:58:54 +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
29 changed files with 3660 additions and 3592 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ public/style.css
public/highlight.css public/highlight.css
public/tiptap public/tiptap
public/lightbox public/lightbox
public/yggdrasil-core
public/doc public/doc
/static /static
.env .env

View File

@ -15,7 +15,7 @@ make build # build-editor → highlight-css → tailwindcss → doc →
make build-linux # same as build but targets x86_64-unknown-linux-musl make build-linux # same as build but targets x86_64-unknown-linux-musl
make css # one-shot CSS make css # one-shot CSS
make css-watch # watch mode make css-watch # watch mode
make test # cargo test + vitest (tiptap-editor + lightbox libs) make test # cargo test + vitest (tiptap-editor + lightbox + yggdrasil-core libs)
make doc # cargo doc (ayu 主题) → 拷贝到 public/doc/,随 build 发布 make doc # cargo doc (ayu 主题) → 拷贝到 public/doc/,随 build 发布
make doc-open # 同 doc生成后自动用浏览器打开本地预览不拷贝 make doc-open # 同 doc生成后自动用浏览器打开本地预览不拷贝
make clean # cargo clean + rm public/style.css make clean # cargo clean + rm public/style.css
@ -150,6 +150,18 @@ Image lightbox (click-to-zoom) in `libs/lightbox/`, built as an IIFE library. Un
Do not edit `public/lightbox/` — they are build artifacts. Do not edit `public/lightbox/` — they are build artifacts.
## Yggdrasil-Core Subproject
Core JavaScript bundle in `libs/yggdrasil-core/`, built as an IIFE library exposing `window.__initPostContent` and `window.__startThemeTransition` (and future core JS entry points). This is the designated home for all new core JS — add to it rather than creating new `public/js/` scripts.
- Output: `public/yggdrasil-core/` (`yggdrasil-core.js`, `yggdrasil-core.css`, `yggdrasil-core.js.map`)
- `make build` runs `npm ci --include=dev && npm run build` inside `libs/yggdrasil-core`; the `build` script is `tsc --noEmit && vite build` (Vite 8 / Rolldown, type-check before bundle). Source is ES module, output is IIFE (`formats: ['iife']` in `vite.config.ts`) because Dioxus `[web.resource] script` injects bare `<script src>` without `type="module"` support.
- Injected globally via `Dioxus.toml` (`script` + `style` arrays). Rust calls the entry points via `js_sys::eval("window.__xxx(...)")` with an `if (window.__xxx)` guard to survive script-load ordering races.
- Unit tests: `npm test` (Vitest 4 + happy-dom), covering `post-content` copy-button lifecycle and `theme-transition` fallback paths (happy-dom lacks `startViewTransition`, naturally covering the degradation path).
- Theme reveal animation uses the View Transitions API: JS synchronously toggles the `dark` class inside `startViewTransition`'s callback (so the browser snapshots the new state), CSS `@keyframes tt-reveal` expands `::view-transition-new(root)` via `clip-path: circle()` from the click point; Rust's `theme.set()` aligns state afterward (idempotent — `use_effect` sets the same class again). `prefers-reduced-motion` and browsers without VT fall back to instant switch.
Do not edit `public/yggdrasil-core/` — they are build artifacts.
## Syntax Highlighting Pipeline ## Syntax Highlighting Pipeline
- `themes/` contains Catppuccin Latte (light) and Mocha (dark) `.tmTheme` files - `themes/` contains Catppuccin Latte (light) and Mocha (dark) `.tmTheme` files
@ -173,7 +185,7 @@ Do not edit `public/lightbox/` — they are build artifacts.
## Testing ## Testing
```bash ```bash
make test # cargo test (Rust, 402 tests) + vitest (tiptap-editor 46 tests, lightbox 23 tests) make test # cargo test (Rust, 402 tests) + vitest (tiptap-editor 46 tests, lightbox 23 tests, yggdrasil-core 9 tests)
dx check # Dioxus type-check (catches component/Router issues) dx check # Dioxus type-check (catches component/Router issues)
cargo clippy # lint cargo clippy # lint
``` ```
@ -193,8 +205,9 @@ Most tests use `#[cfg(all(test, feature = "server"))]` — they only run when th
- `public/highlight.css` — generated by `generate_highlight_css` binary - `public/highlight.css` — generated by `generate_highlight_css` binary
- `public/tiptap/` — Vite build output (editor) - `public/tiptap/` — Vite build output (editor)
- `public/lightbox/` — Vite build output (lightbox) - `public/lightbox/` — Vite build output (lightbox)
- `public/yggdrasil-core/` — Vite build output (core JS bundle)
- `/dist`, `/.dioxus`, `/target` - `/dist`, `/.dioxus`, `/target`
- `node_modules` (inside `libs/tiptap-editor/` and `libs/lightbox/`) - `node_modules` (inside `libs/tiptap-editor/`, `libs/lightbox/`, and `libs/yggdrasil-core/`)
- `uploads/.cache/` — image processing disk cache - `uploads/.cache/` — image processing disk cache
## Notes ## Notes

View File

@ -10,9 +10,9 @@ title = "Yggdrasil - Dioxus SSR"
watch_path = ["src", "Cargo.toml"] watch_path = ["src", "Cargo.toml"]
[web.resource] [web.resource]
style = ["/style.css", "/highlight.css", "/tiptap/editor.css", "/lightbox/lightbox.css"] style = ["/style.css", "/highlight.css", "/tiptap/editor.css", "/lightbox/lightbox.css", "/yggdrasil-core/yggdrasil-core.css"]
script = ["/tiptap/editor.js", "/lightbox/lightbox.js"] script = ["/tiptap/editor.js", "/lightbox/lightbox.js", "/yggdrasil-core/yggdrasil-core.js"]
[web.resource.dev] [web.resource.dev]
style = ["/style.css", "/highlight.css", "/tiptap/editor.css", "/lightbox/lightbox.css"] style = ["/style.css", "/highlight.css", "/tiptap/editor.css", "/lightbox/lightbox.css", "/yggdrasil-core/yggdrasil-core.css"]
script = ["/tiptap/editor.js", "/lightbox/lightbox.js"] script = ["/tiptap/editor.js", "/lightbox/lightbox.js", "/yggdrasil-core/yggdrasil-core.js"]

View File

@ -1,43 +1,59 @@
.PHONY: dev build build-linux css css-watch clean build-editor build-editor-incremental build-lightbox build-lightbox-incremental highlight-css test doc doc-open .PHONY: dev build build-linux css css-watch clean build-editor build-editor-incremental build-lightbox build-lightbox-incremental build-core build-core-incremental highlight-css test doc doc-open start
build: build:
@$(MAKE) build-editor @$(MAKE) build-editor
@$(MAKE) build-lightbox @$(MAKE) build-lightbox
@$(MAKE) build-core
@$(MAKE) highlight-css @$(MAKE) highlight-css
@tailwindcss -i input.css -o public/style.css --minify @tailwindcss -i input.css -o public/style.css --minify
@$(MAKE) doc
@dx build --release --debug-symbols=false @dx build --release --debug-symbols=false
build-linux: build-linux:
@$(MAKE) build-editor @$(MAKE) build-editor
@$(MAKE) build-lightbox @$(MAKE) build-lightbox
@$(MAKE) build-core
@$(MAKE) highlight-css @$(MAKE) highlight-css
@tailwindcss -i input.css -o public/style.css --minify @tailwindcss -i input.css -o public/style.css --minify
@dx build @client --release --debug-symbols=false --wasm-js-cfg false @dx build @client --release --debug-symbols=false --wasm-js-cfg false
@dx build @server --release --debug-symbols=false --target x86_64-unknown-linux-musl --wasm-js-cfg false --features server @dx build @server --release --debug-symbols=false --target x86_64-unknown-linux-musl --wasm-js-cfg false --features server
@echo ""
@echo "Linux build complete! The server binary is at target/dx/yggdrasil/release/web/server"
@echo "Remember to deploy it alongside the target/dx/yggdrasil/release/web/public directory."
@echo "When running the server, ensure DIOXUS_ASSET_DIR is set or the public directory is in CWD."
highlight-css: highlight-css:
@cargo run --bin generate_highlight_css @cargo run --bin generate_highlight_css
build-editor: build-editor:
@echo "Building Tiptap editor..." @echo "Building Tiptap editor..."
@cd libs/tiptap-editor && npm ci --include=dev && npm run build @cd libs/tiptap-editor && pnpm ci --include=dev && pnpm run build
@echo "Tiptap editor built." @echo "Tiptap editor built."
# dev 用的增量构建:跳过 npm ci假设 node_modules 已存在),仅 vite build。 # dev 用的增量构建:跳过 pnpm ci假设 node_modules 已存在),仅 vite build。
# 与 build-editor 分开,避免每次 make dev 都重装依赖。 # 与 build-editor 分开,避免每次 make dev 都重装依赖。
build-editor-incremental: build-editor-incremental:
@cd libs/tiptap-editor && npm run build @cd libs/tiptap-editor && pnpm run build
build-lightbox: build-lightbox:
@echo "Building Lightbox..." @echo "Building Lightbox..."
@cd libs/lightbox && npm install && npm run build @cd libs/lightbox && pnpm install && pnpm run build
@echo "Lightbox built." @echo "Lightbox built."
# dev 用的增量构建:跳过 npm ci假设 node_modules 已存在),仅 vite build。 # dev 用的增量构建:跳过 pnpm ci假设 node_modules 已存在),仅 vite build。
build-lightbox-incremental: build-lightbox-incremental:
@cd libs/lightbox && npm run build @cd libs/lightbox && pnpm run build
dev: build-editor-incremental build-lightbox-incremental build-core:
@echo "Building yggdrasil-core..."
@cd libs/yggdrasil-core && pnpm install && pnpm run build
@echo "yggdrasil-core built."
# dev 用的增量构建:跳过 pnpm install假设 node_modules 已存在),仅 vite build。
build-core-incremental:
@cd libs/yggdrasil-core && pnpm run build
dev: build-editor-incremental build-lightbox-incremental build-core-incremental
@echo "Cleaning static/..." @echo "Cleaning static/..."
@rm -rf static/ @rm -rf static/
@echo "Building Tiptap editor (incremental)..." @echo "Building Tiptap editor (incremental)..."
@ -55,16 +71,30 @@ css-watch:
test: test:
@cargo test @cargo test
@cd libs/tiptap-editor && npm test @cd libs/tiptap-editor && pnpm test
@cd libs/lightbox && npm test @cd libs/lightbox && pnpm test
@cd libs/yggdrasil-core && pnpm test
# 只编译当前 crate 的文档(--no-deps 跳过依赖,--document-private-items # 只编译当前 crate 的文档(--no-deps 跳过依赖,--document-private-items
# 让纯 binary crate 的内部模块/私有项也进文档,否则页面基本是空的)。 # 让纯 binary crate 的内部模块/私有项也进文档,否则页面基本是空的)。
# RUSTDOCFLAGS 把 rustdoc 的 --default-theme=ayu 透传过去——cargo doc 本身 # RUSTDOCFLAGS 把 rustdoc 的 --default-theme=ayu 透传过去——cargo doc 本身
# 无主题参数,但会把该环境变量转交给底层 rustdoc。注意它是默认值浏览器 # 无主题参数,但会把该环境变量转交给底层 rustdoc。注意它是默认值浏览器
# 若已记住上次的主题选择localStorage则不会被覆盖。 # 若已记住上次的主题选择localStorage则不会被覆盖。
#
# 生成后拷贝到 public/doc/,让文档随 Dioxus 静态目录发布。先清空旧目录再
# 整体拷贝避免删除模块后残留旧文件。rustdoc 内部用相对路径引用资源
# (如 ../../static.files/),原样挂载不会断链。
#
# 额外生成 public/doc/index.html 重定向页Dioxus 在 dev 用
# nest_service("/doc", ServeDir) 托管该目录ServeDir 访问目录根时默认
# 返回 index.html。用 meta refresh + JS 跳转到真正的文档入口
# yggdrasil/index.html这样裸路径 /doc 也能直达文档,且不与 Dioxus 的
# /doc/* 路由冲突(手动注册 /doc 会在 merge 时 panic
doc: doc:
@RUSTDOCFLAGS="--default-theme=ayu" cargo doc --no-deps --document-private-items @RUSTDOCFLAGS="--default-theme=ayu" cargo doc --no-deps --document-private-items
@rm -rf public/doc
@cp -r target/doc public/doc
@printf '<!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0;url=yggdrasil/index.html"><title>Redirecting…</title></head><body><script>location.replace("yggdrasil/index.html")</script></body></html>' > public/doc/index.html
# 同 doc生成完自动用浏览器打开。 # 同 doc生成完自动用浏览器打开。
doc-open: doc-open:
@ -73,4 +103,5 @@ doc-open:
clean: clean:
@cargo clean @cargo clean
@rm -f public/style.css public/highlight.css @rm -f public/style.css public/highlight.css
@rm -rf public/doc
@rm -rf uploads/.cache @rm -rf uploads/.cache

View File

@ -29,13 +29,13 @@
@layer base { @layer base {
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
background-color: var(--color-paper-theme);
} }
body { body {
background-color: var(--color-paper-theme); background-color: var(--color-paper-theme);
color: var(--color-paper-primary); color: var(--color-paper-primary);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
transition: background-color 0.3s ease, color 0.3s ease;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }

File diff suppressed because it is too large Load Diff

849
libs/lightbox/pnpm-lock.yaml generated Normal file
View File

@ -0,0 +1,849 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
happy-dom:
specifier: ^20.10.6
version: 20.10.6
typescript:
specifier: ^6.0.3
version: 6.0.3
vite:
specifier: ^8.1.0
version: 8.1.0(@types/node@26.0.1)
vitest:
specifier: ^4.1.9
version: 4.1.9(@types/node@26.0.1)(happy-dom@20.10.6)(vite@8.1.0(@types/node@26.0.1))
packages:
'@emnapi/core@1.11.1':
resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
'@emnapi/runtime@1.11.1':
resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
'@emnapi/wasi-threads@1.2.2':
resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@napi-rs/wasm-runtime@1.1.6':
resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@oxc-project/types@0.137.0':
resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==}
'@rolldown/binding-android-arm64@1.1.3':
resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.1.3':
resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.1.3':
resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.1.3':
resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.1.3':
resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.1.3':
resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.1.3':
resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.1.3':
resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.1.3':
resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.1.3':
resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.1.3':
resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.1.3':
resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.1.3':
resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.1.3':
resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.1.3':
resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@tybys/wasm-util@0.10.3':
resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
'@types/node@26.0.1':
resolution: {integrity: sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==}
'@types/whatwg-mimetype@3.0.2':
resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@vitest/expect@4.1.9':
resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
'@vitest/mocker@4.1.9':
resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.1.9':
resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
'@vitest/runner@4.1.9':
resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
'@vitest/snapshot@4.1.9':
resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
'@vitest/spy@4.1.9':
resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
'@vitest/utils@4.1.9':
resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
buffer-image-size@0.6.4:
resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
engines: {node: '>=4.0'}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
entities@7.0.1:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'}
es-module-lexer@2.1.0:
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
expect-type@1.4.0:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
happy-dom@20.10.6:
resolution: {integrity: sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==}
engines: {node: '>=20.0.0'}
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.32.0:
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.32.0:
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.32.0:
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
nanoid@3.3.15:
resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
obug@2.1.3:
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
engines: {node: '>=12.20.0'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
postcss@8.5.15:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
rolldown@1.1.3:
resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@1.2.4:
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
engines: {node: '>=18'}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@6.0.3:
resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@8.3.0:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
vite@8.1.0:
resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.3.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
'@vitejs/devtools':
optional: true
esbuild:
optional: true
jiti:
optional: true
less:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vitest@4.1.9:
resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.9
'@vitest/browser-preview': 4.1.9
'@vitest/browser-webdriverio': 4.1.9
'@vitest/coverage-istanbul': 4.1.9
'@vitest/coverage-v8': 4.1.9
'@vitest/ui': 4.1.9
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
snapshots:
'@emnapi/core@1.11.1':
dependencies:
'@emnapi/wasi-threads': 1.2.2
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.11.1':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/wasi-threads@1.2.2':
dependencies:
tslib: 2.8.1
optional: true
'@jridgewell/sourcemap-codec@1.5.5': {}
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
'@emnapi/core': 1.11.1
'@emnapi/runtime': 1.11.1
'@tybys/wasm-util': 0.10.3
optional: true
'@oxc-project/types@0.137.0': {}
'@rolldown/binding-android-arm64@1.1.3':
optional: true
'@rolldown/binding-darwin-arm64@1.1.3':
optional: true
'@rolldown/binding-darwin-x64@1.1.3':
optional: true
'@rolldown/binding-freebsd-x64@1.1.3':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.1.3':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-arm64-musl@1.1.3':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-x64-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-x64-musl@1.1.3':
optional: true
'@rolldown/binding-openharmony-arm64@1.1.3':
optional: true
'@rolldown/binding-wasm32-wasi@1.1.3':
dependencies:
'@emnapi/core': 1.11.1
'@emnapi/runtime': 1.11.1
'@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
optional: true
'@rolldown/binding-win32-arm64-msvc@1.1.3':
optional: true
'@rolldown/binding-win32-x64-msvc@1.1.3':
optional: true
'@rolldown/pluginutils@1.0.1': {}
'@standard-schema/spec@1.1.0': {}
'@tybys/wasm-util@0.10.3':
dependencies:
tslib: 2.8.1
optional: true
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.9': {}
'@types/node@26.0.1':
dependencies:
undici-types: 8.3.0
'@types/whatwg-mimetype@3.0.2': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 26.0.1
'@vitest/expect@4.1.9':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.9
'@vitest/utils': 4.1.9
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@4.1.9(vite@8.1.0(@types/node@26.0.1))':
dependencies:
'@vitest/spy': 4.1.9
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.1.0(@types/node@26.0.1)
'@vitest/pretty-format@4.1.9':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@4.1.9':
dependencies:
'@vitest/utils': 4.1.9
pathe: 2.0.3
'@vitest/snapshot@4.1.9':
dependencies:
'@vitest/pretty-format': 4.1.9
'@vitest/utils': 4.1.9
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.1.9': {}
'@vitest/utils@4.1.9':
dependencies:
'@vitest/pretty-format': 4.1.9
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
assertion-error@2.0.1: {}
buffer-image-size@0.6.4:
dependencies:
'@types/node': 26.0.1
chai@6.2.2: {}
convert-source-map@2.0.0: {}
detect-libc@2.1.2: {}
entities@7.0.1: {}
es-module-lexer@2.1.0: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.9
expect-type@1.4.0: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
fsevents@2.3.3:
optional: true
happy-dom@20.10.6:
dependencies:
'@types/node': 26.0.1
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
buffer-image-size: 0.6.4
entities: 7.0.1
whatwg-mimetype: 3.0.0
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
lightningcss-android-arm64@1.32.0:
optional: true
lightningcss-darwin-arm64@1.32.0:
optional: true
lightningcss-darwin-x64@1.32.0:
optional: true
lightningcss-freebsd-x64@1.32.0:
optional: true
lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
lightningcss-linux-arm64-musl@1.32.0:
optional: true
lightningcss-linux-x64-gnu@1.32.0:
optional: true
lightningcss-linux-x64-musl@1.32.0:
optional: true
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
lightningcss-win32-x64-msvc@1.32.0:
optional: true
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-android-arm64: 1.32.0
lightningcss-darwin-arm64: 1.32.0
lightningcss-darwin-x64: 1.32.0
lightningcss-freebsd-x64: 1.32.0
lightningcss-linux-arm-gnueabihf: 1.32.0
lightningcss-linux-arm64-gnu: 1.32.0
lightningcss-linux-arm64-musl: 1.32.0
lightningcss-linux-x64-gnu: 1.32.0
lightningcss-linux-x64-musl: 1.32.0
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
nanoid@3.3.15: {}
obug@2.1.3: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
picomatch@4.0.4: {}
postcss@8.5.15:
dependencies:
nanoid: 3.3.15
picocolors: 1.1.1
source-map-js: 1.2.1
rolldown@1.1.3:
dependencies:
'@oxc-project/types': 0.137.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm64': 1.1.3
'@rolldown/binding-darwin-arm64': 1.1.3
'@rolldown/binding-darwin-x64': 1.1.3
'@rolldown/binding-freebsd-x64': 1.1.3
'@rolldown/binding-linux-arm-gnueabihf': 1.1.3
'@rolldown/binding-linux-arm64-gnu': 1.1.3
'@rolldown/binding-linux-arm64-musl': 1.1.3
'@rolldown/binding-linux-ppc64-gnu': 1.1.3
'@rolldown/binding-linux-s390x-gnu': 1.1.3
'@rolldown/binding-linux-x64-gnu': 1.1.3
'@rolldown/binding-linux-x64-musl': 1.1.3
'@rolldown/binding-openharmony-arm64': 1.1.3
'@rolldown/binding-wasm32-wasi': 1.1.3
'@rolldown/binding-win32-arm64-msvc': 1.1.3
'@rolldown/binding-win32-x64-msvc': 1.1.3
siginfo@2.0.0: {}
source-map-js@1.2.1: {}
stackback@0.0.2: {}
std-env@4.1.0: {}
tinybench@2.9.0: {}
tinyexec@1.2.4: {}
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyrainbow@3.1.0: {}
tslib@2.8.1:
optional: true
typescript@6.0.3: {}
undici-types@8.3.0: {}
vite@8.1.0(@types/node@26.0.1):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
postcss: 8.5.15
rolldown: 1.1.3
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.0.1
fsevents: 2.3.3
vitest@4.1.9(@types/node@26.0.1)(happy-dom@20.10.6)(vite@8.1.0(@types/node@26.0.1)):
dependencies:
'@vitest/expect': 4.1.9
'@vitest/mocker': 4.1.9(vite@8.1.0(@types/node@26.0.1))
'@vitest/pretty-format': 4.1.9
'@vitest/runner': 4.1.9
'@vitest/snapshot': 4.1.9
'@vitest/spy': 4.1.9
'@vitest/utils': 4.1.9
es-module-lexer: 2.1.0
expect-type: 1.4.0
magic-string: 0.30.21
obug: 2.1.3
pathe: 2.0.3
picomatch: 4.0.4
std-env: 4.1.0
tinybench: 2.9.0
tinyexec: 1.2.4
tinyglobby: 0.2.17
tinyrainbow: 3.1.0
vite: 8.1.0(@types/node@26.0.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 26.0.1
happy-dom: 20.10.6
transitivePeerDependencies:
- msw
whatwg-mimetype@3.0.0: {}
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
ws@8.21.0: {}

File diff suppressed because it is too large Load Diff

1374
libs/tiptap-editor/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

1
libs/yggdrasil-core/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,19 @@
{
"name": "@yggdrasil/core",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc --noEmit && vite build",
"dev": "vite",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest"
},
"devDependencies": {
"happy-dom": "^20.10.6",
"typescript": "^6.0.3",
"vite": "^8.1.0",
"vitest": "^4.1.9"
}
}

849
libs/yggdrasil-core/pnpm-lock.yaml generated Normal file
View File

@ -0,0 +1,849 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
happy-dom:
specifier: ^20.10.6
version: 20.10.6
typescript:
specifier: ^6.0.3
version: 6.0.3
vite:
specifier: ^8.1.0
version: 8.1.0(@types/node@26.0.1)
vitest:
specifier: ^4.1.9
version: 4.1.9(@types/node@26.0.1)(happy-dom@20.10.6)(vite@8.1.0(@types/node@26.0.1))
packages:
'@emnapi/core@1.11.1':
resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==}
'@emnapi/runtime@1.11.1':
resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==}
'@emnapi/wasi-threads@1.2.2':
resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
'@napi-rs/wasm-runtime@1.1.6':
resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==}
peerDependencies:
'@emnapi/core': ^1.7.1
'@emnapi/runtime': ^1.7.1
'@oxc-project/types@0.137.0':
resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==}
'@rolldown/binding-android-arm64@1.1.3':
resolution: {integrity: sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
'@rolldown/binding-darwin-arm64@1.1.3':
resolution: {integrity: sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
'@rolldown/binding-darwin-x64@1.1.3':
resolution: {integrity: sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
'@rolldown/binding-freebsd-x64@1.1.3':
resolution: {integrity: sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
'@rolldown/binding-linux-arm-gnueabihf@1.1.3':
resolution: {integrity: sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
'@rolldown/binding-linux-arm64-gnu@1.1.3':
resolution: {integrity: sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-arm64-musl@1.1.3':
resolution: {integrity: sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rolldown/binding-linux-ppc64-gnu@1.1.3':
resolution: {integrity: sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-s390x-gnu@1.1.3':
resolution: {integrity: sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-gnu@1.1.3':
resolution: {integrity: sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rolldown/binding-linux-x64-musl@1.1.3':
resolution: {integrity: sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
'@rolldown/binding-openharmony-arm64@1.1.3':
resolution: {integrity: sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
'@rolldown/binding-wasm32-wasi@1.1.3':
resolution: {integrity: sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [wasm32]
'@rolldown/binding-win32-arm64-msvc@1.1.3':
resolution: {integrity: sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
'@rolldown/binding-win32-x64-msvc@1.1.3':
resolution: {integrity: sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@tybys/wasm-util@0.10.3':
resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
'@types/node@26.0.1':
resolution: {integrity: sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==}
'@types/whatwg-mimetype@3.0.2':
resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==}
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
'@vitest/expect@4.1.9':
resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==}
'@vitest/mocker@4.1.9':
resolution: {integrity: sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.1.9':
resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==}
'@vitest/runner@4.1.9':
resolution: {integrity: sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==}
'@vitest/snapshot@4.1.9':
resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==}
'@vitest/spy@4.1.9':
resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==}
'@vitest/utils@4.1.9':
resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
buffer-image-size@0.6.4:
resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==}
engines: {node: '>=4.0'}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
entities@7.0.1:
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
engines: {node: '>=0.12'}
es-module-lexer@2.1.0:
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
expect-type@1.4.0:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
peerDependencies:
picomatch: ^3 || ^4
peerDependenciesMeta:
picomatch:
optional: true
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
happy-dom@20.10.6:
resolution: {integrity: sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==}
engines: {node: '>=20.0.0'}
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [android]
lightningcss-darwin-arm64@1.32.0:
resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [darwin]
lightningcss-darwin-x64@1.32.0:
resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [darwin]
lightningcss-freebsd-x64@1.32.0:
resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [freebsd]
lightningcss-linux-arm-gnueabihf@1.32.0:
resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
engines: {node: '>= 12.0.0'}
cpu: [arm]
os: [linux]
lightningcss-linux-arm64-gnu@1.32.0:
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
lightningcss-linux-arm64-musl@1.32.0:
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
lightningcss-linux-x64-gnu@1.32.0:
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
lightningcss-linux-x64-musl@1.32.0:
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
libc: [musl]
lightningcss-win32-arm64-msvc@1.32.0:
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [win32]
lightningcss-win32-x64-msvc@1.32.0:
resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [win32]
lightningcss@1.32.0:
resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
engines: {node: '>= 12.0.0'}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
nanoid@3.3.15:
resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
obug@2.1.3:
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
engines: {node: '>=12.20.0'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
postcss@8.5.15:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
rolldown@1.1.3:
resolution: {integrity: sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
std-env@4.1.0:
resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@1.2.4:
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
engines: {node: '>=18'}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.1.0:
resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
typescript@6.0.3:
resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'}
hasBin: true
undici-types@8.3.0:
resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
vite@8.1.0:
resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
'@vitejs/devtools': ^0.3.0
esbuild: ^0.27.0 || ^0.28.0
jiti: '>=1.21.0'
less: ^4.0.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
'@vitejs/devtools':
optional: true
esbuild:
optional: true
jiti:
optional: true
less:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vitest@4.1.9:
resolution: {integrity: sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.1.9
'@vitest/browser-preview': 4.1.9
'@vitest/browser-webdriverio': 4.1.9
'@vitest/coverage-istanbul': 4.1.9
'@vitest/coverage-v8': 4.1.9
'@vitest/ui': 4.1.9
happy-dom: '*'
jsdom: '*'
vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/coverage-istanbul':
optional: true
'@vitest/coverage-v8':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
whatwg-mimetype@3.0.0:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
ws@8.21.0:
resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
snapshots:
'@emnapi/core@1.11.1':
dependencies:
'@emnapi/wasi-threads': 1.2.2
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.11.1':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/wasi-threads@1.2.2':
dependencies:
tslib: 2.8.1
optional: true
'@jridgewell/sourcemap-codec@1.5.5': {}
'@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)':
dependencies:
'@emnapi/core': 1.11.1
'@emnapi/runtime': 1.11.1
'@tybys/wasm-util': 0.10.3
optional: true
'@oxc-project/types@0.137.0': {}
'@rolldown/binding-android-arm64@1.1.3':
optional: true
'@rolldown/binding-darwin-arm64@1.1.3':
optional: true
'@rolldown/binding-darwin-x64@1.1.3':
optional: true
'@rolldown/binding-freebsd-x64@1.1.3':
optional: true
'@rolldown/binding-linux-arm-gnueabihf@1.1.3':
optional: true
'@rolldown/binding-linux-arm64-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-arm64-musl@1.1.3':
optional: true
'@rolldown/binding-linux-ppc64-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-s390x-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-x64-gnu@1.1.3':
optional: true
'@rolldown/binding-linux-x64-musl@1.1.3':
optional: true
'@rolldown/binding-openharmony-arm64@1.1.3':
optional: true
'@rolldown/binding-wasm32-wasi@1.1.3':
dependencies:
'@emnapi/core': 1.11.1
'@emnapi/runtime': 1.11.1
'@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)
optional: true
'@rolldown/binding-win32-arm64-msvc@1.1.3':
optional: true
'@rolldown/binding-win32-x64-msvc@1.1.3':
optional: true
'@rolldown/pluginutils@1.0.1': {}
'@standard-schema/spec@1.1.0': {}
'@tybys/wasm-util@0.10.3':
dependencies:
tslib: 2.8.1
optional: true
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.9': {}
'@types/node@26.0.1':
dependencies:
undici-types: 8.3.0
'@types/whatwg-mimetype@3.0.2': {}
'@types/ws@8.18.1':
dependencies:
'@types/node': 26.0.1
'@vitest/expect@4.1.9':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.1.9
'@vitest/utils': 4.1.9
chai: 6.2.2
tinyrainbow: 3.1.0
'@vitest/mocker@4.1.9(vite@8.1.0(@types/node@26.0.1))':
dependencies:
'@vitest/spy': 4.1.9
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 8.1.0(@types/node@26.0.1)
'@vitest/pretty-format@4.1.9':
dependencies:
tinyrainbow: 3.1.0
'@vitest/runner@4.1.9':
dependencies:
'@vitest/utils': 4.1.9
pathe: 2.0.3
'@vitest/snapshot@4.1.9':
dependencies:
'@vitest/pretty-format': 4.1.9
'@vitest/utils': 4.1.9
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.1.9': {}
'@vitest/utils@4.1.9':
dependencies:
'@vitest/pretty-format': 4.1.9
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
assertion-error@2.0.1: {}
buffer-image-size@0.6.4:
dependencies:
'@types/node': 26.0.1
chai@6.2.2: {}
convert-source-map@2.0.0: {}
detect-libc@2.1.2: {}
entities@7.0.1: {}
es-module-lexer@2.1.0: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.9
expect-type@1.4.0: {}
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
picomatch: 4.0.4
fsevents@2.3.3:
optional: true
happy-dom@20.10.6:
dependencies:
'@types/node': 26.0.1
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
buffer-image-size: 0.6.4
entities: 7.0.1
whatwg-mimetype: 3.0.0
ws: 8.21.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
lightningcss-android-arm64@1.32.0:
optional: true
lightningcss-darwin-arm64@1.32.0:
optional: true
lightningcss-darwin-x64@1.32.0:
optional: true
lightningcss-freebsd-x64@1.32.0:
optional: true
lightningcss-linux-arm-gnueabihf@1.32.0:
optional: true
lightningcss-linux-arm64-gnu@1.32.0:
optional: true
lightningcss-linux-arm64-musl@1.32.0:
optional: true
lightningcss-linux-x64-gnu@1.32.0:
optional: true
lightningcss-linux-x64-musl@1.32.0:
optional: true
lightningcss-win32-arm64-msvc@1.32.0:
optional: true
lightningcss-win32-x64-msvc@1.32.0:
optional: true
lightningcss@1.32.0:
dependencies:
detect-libc: 2.1.2
optionalDependencies:
lightningcss-android-arm64: 1.32.0
lightningcss-darwin-arm64: 1.32.0
lightningcss-darwin-x64: 1.32.0
lightningcss-freebsd-x64: 1.32.0
lightningcss-linux-arm-gnueabihf: 1.32.0
lightningcss-linux-arm64-gnu: 1.32.0
lightningcss-linux-arm64-musl: 1.32.0
lightningcss-linux-x64-gnu: 1.32.0
lightningcss-linux-x64-musl: 1.32.0
lightningcss-win32-arm64-msvc: 1.32.0
lightningcss-win32-x64-msvc: 1.32.0
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
nanoid@3.3.15: {}
obug@2.1.3: {}
pathe@2.0.3: {}
picocolors@1.1.1: {}
picomatch@4.0.4: {}
postcss@8.5.15:
dependencies:
nanoid: 3.3.15
picocolors: 1.1.1
source-map-js: 1.2.1
rolldown@1.1.3:
dependencies:
'@oxc-project/types': 0.137.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
'@rolldown/binding-android-arm64': 1.1.3
'@rolldown/binding-darwin-arm64': 1.1.3
'@rolldown/binding-darwin-x64': 1.1.3
'@rolldown/binding-freebsd-x64': 1.1.3
'@rolldown/binding-linux-arm-gnueabihf': 1.1.3
'@rolldown/binding-linux-arm64-gnu': 1.1.3
'@rolldown/binding-linux-arm64-musl': 1.1.3
'@rolldown/binding-linux-ppc64-gnu': 1.1.3
'@rolldown/binding-linux-s390x-gnu': 1.1.3
'@rolldown/binding-linux-x64-gnu': 1.1.3
'@rolldown/binding-linux-x64-musl': 1.1.3
'@rolldown/binding-openharmony-arm64': 1.1.3
'@rolldown/binding-wasm32-wasi': 1.1.3
'@rolldown/binding-win32-arm64-msvc': 1.1.3
'@rolldown/binding-win32-x64-msvc': 1.1.3
siginfo@2.0.0: {}
source-map-js@1.2.1: {}
stackback@0.0.2: {}
std-env@4.1.0: {}
tinybench@2.9.0: {}
tinyexec@1.2.4: {}
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyrainbow@3.1.0: {}
tslib@2.8.1:
optional: true
typescript@6.0.3: {}
undici-types@8.3.0: {}
vite@8.1.0(@types/node@26.0.1):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
postcss: 8.5.15
rolldown: 1.1.3
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 26.0.1
fsevents: 2.3.3
vitest@4.1.9(@types/node@26.0.1)(happy-dom@20.10.6)(vite@8.1.0(@types/node@26.0.1)):
dependencies:
'@vitest/expect': 4.1.9
'@vitest/mocker': 4.1.9(vite@8.1.0(@types/node@26.0.1))
'@vitest/pretty-format': 4.1.9
'@vitest/runner': 4.1.9
'@vitest/snapshot': 4.1.9
'@vitest/spy': 4.1.9
'@vitest/utils': 4.1.9
es-module-lexer: 2.1.0
expect-type: 1.4.0
magic-string: 0.30.21
obug: 2.1.3
pathe: 2.0.3
picomatch: 4.0.4
std-env: 4.1.0
tinybench: 2.9.0
tinyexec: 1.2.4
tinyglobby: 0.2.17
tinyrainbow: 3.1.0
vite: 8.1.0(@types/node@26.0.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 26.0.1
happy-dom: 20.10.6
transitivePeerDependencies:
- msw
whatwg-mimetype@3.0.0: {}
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
ws@8.21.0: {}

View File

@ -0,0 +1,15 @@
import { initPostContent } from './post-content';
import { startThemeTransition } from './theme-transition';
import './style.css';
declare global {
interface Window {
__initPostContent: (selector: string) => void;
__startThemeTransition: (x: number, y: number) => void;
}
}
window.__initPostContent = initPostContent;
window.__startThemeTransition = startThemeTransition;
export {};

View File

@ -0,0 +1,73 @@
/**
* post-content 测试:钉住代码块 copy
* 黑盒驱动:只通过 window.__initPostContent
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import './index';
describe('initPostContent', () => {
beforeEach(() => {
document.body.innerHTML = '';
vi.restoreAllMocks();
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
document.body.innerHTML = '';
});
it('为 pre>code 注入 .copy-code 按钮', () => {
const root = document.createElement('div');
root.className = 'post-content';
root.innerHTML = '<pre><code>console.log(1)</code></pre>';
document.body.appendChild(root);
window.__initPostContent('.post-content');
const btn = root.querySelector('pre .copy-code');
expect(btn).not.toBeNull();
expect(btn?.textContent).toBe('copy');
});
it('已有 .copy-code 的 pre 不重复注入', () => {
const root = document.createElement('div');
root.className = 'post-content';
root.innerHTML =
'<pre><code>x</code><button class="copy-code">copy</button></pre>';
document.body.appendChild(root);
window.__initPostContent('.post-content');
const btns = root.querySelectorAll('pre .copy-code');
expect(btns.length).toBe(1);
});
it('点击按钮调用 clipboard.writeText 并回显 copied!', () => {
const writeText = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, 'clipboard', {
value: { writeText },
configurable: true,
});
const root = document.createElement('div');
root.className = 'post-content';
root.innerHTML = '<pre><code>hello</code></pre>';
document.body.appendChild(root);
window.__initPostContent('.post-content');
const btn = root.querySelector('.copy-code') as HTMLButtonElement;
btn.click();
expect(writeText).toHaveBeenCalledWith('hello');
expect(btn.textContent).toBe('copied!');
// 2 秒后还原回 'copy'
vi.advanceTimersByTime(2000);
expect(btn.textContent).toBe('copy');
});
it('selector 未命中时不报错', () => {
expect(() => window.__initPostContent('.not-exist')).not.toThrow();
});
});

View File

@ -0,0 +1,48 @@
/**
* copy
*
* pre>code .copy-code , copied!
* 2 input.css .copy-code ( Tailwind )
*/
function initCopyButtons(root: Element): void {
const blocks = root.querySelectorAll('pre > code');
blocks.forEach((code) => {
const pre = code.parentElement;
if (!pre) return;
if (pre.querySelector('.copy-code')) return;
const btn = document.createElement('button');
btn.className = 'copy-code';
btn.textContent = 'copy';
btn.setAttribute('aria-label', 'Copy code');
const codeText = code.textContent || '';
btn.addEventListener('click', () => {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(codeText);
} else {
const ta = document.createElement('textarea');
ta.value = codeText;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
}
btn.textContent = 'copied!';
setTimeout(() => {
btn.textContent = 'copy';
}, 2000);
});
pre.appendChild(btn);
});
}
export function initPostContent(selector: string): void {
const root = document.querySelector(selector);
if (!root) return;
initCopyButtons(root);
}

View File

@ -0,0 +1,36 @@
/* ========== 主题切换圆形展开(View Transitions API) ========== */
/* 取消所有 UA 默认的动画和颜色混合 */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
display: block;
}
/* 无论什么方向,始终让 new (新状态) 在上方,并从小圆扩展覆盖 old */
::view-transition-old(root) {
z-index: 1;
}
::view-transition-new(root) {
z-index: 2;
animation: tt-expand 0.4s ease-out forwards;
/* 显式给伪元素加上不透明背景,防止截图透明导致看不到覆盖效果 */
background: var(--color-paper-theme);
}
/* CSS 变量由 JS 注入到 html style 中 */
@keyframes tt-expand {
from { clip-path: circle(0px at var(--tt-x) var(--tt-y)); }
to { clip-path: circle(var(--tt-r) at var(--tt-x) var(--tt-y)); }
}
/* View Transition 期间强制禁用所有 CSS 过渡,
确保 VT 回调中 toggle dark class 后截图捕获的是最终颜色 */
html.is-theme-transitioning,
html.is-theme-transitioning *,
html.is-theme-transitioning *::before,
html.is-theme-transitioning *::after {
transition: none !important;
}

View File

@ -0,0 +1,108 @@
/**
* theme-transition
*
* happy-dom document.startViewTransition,( dark class)
* mock startViewTransition 验证:调用它 CSS
*
* 注意:startThemeTransition (x, y),(/) DOM dark class
* (),
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import './index';
describe('startThemeTransition', () => {
beforeEach(() => {
document.documentElement.classList.remove('dark');
document.documentElement.classList.remove('is-theme-transitioning');
document.documentElement.style.cssText = '';
vi.restoreAllMocks();
});
afterEach(() => {
document.documentElement.classList.remove('dark');
document.documentElement.classList.remove('is-theme-transitioning');
document.documentElement.style.cssText = '';
});
it('降级:无 startViewTransition 时,亮→暗(无 dark class 时 add)', () => {
expect(document.documentElement.classList.contains('dark')).toBe(false);
window.__startThemeTransition(100, 200);
expect(document.documentElement.classList.contains('dark')).toBe(true);
});
it('降级:无 startViewTransition 时,暗→亮(有 dark class 时 remove)', () => {
document.documentElement.classList.add('dark');
window.__startThemeTransition(100, 200);
expect(document.documentElement.classList.contains('dark')).toBe(false);
});
it('主路径:有 startViewTransition 时调用它,注入变量,callback 切换 dark class', async () => {
const cbRef: { cb: (() => void) | null } = { cb: null };
const readyP = Promise.resolve();
const finishedP = Promise.resolve();
const startVT = vi.fn((cb: () => void) => {
cbRef.cb = cb;
return { ready: readyP, finished: finishedP, skipTransition: () => {} };
});
Object.defineProperty(document, 'startViewTransition', {
value: startVT,
configurable: true,
writable: true,
});
window.__startThemeTransition(100, 200);
expect(startVT).toHaveBeenCalledTimes(1);
// CSS 变量应注入
expect(document.documentElement.style.getPropertyValue('--tt-x')).toBe('100px');
expect(document.documentElement.style.getPropertyValue('--tt-y')).toBe('200px');
expect(document.documentElement.style.getPropertyValue('--tt-r')).toMatch(/^\d+(\.\d+)?px$/);
// is-theme-transitioning 应在 VT 之前添加
expect(document.documentElement.classList.contains('is-theme-transitioning')).toBe(true);
// callback 里根据 DOM 现状(无 dark)切到 dark
cbRef.cb!();
expect(document.documentElement.classList.contains('dark')).toBe(true);
// finished 后移除 is-theme-transitioning 和 CSS 变量
await finishedP;
await Promise.resolve();
// happy-dom microtask 可能需要额外 tick
await new Promise((r) => setTimeout(r, 0));
expect(document.documentElement.classList.contains('is-theme-transitioning')).toBe(false);
expect(document.documentElement.style.getPropertyValue('--tt-x')).toBe('');
delete (document as unknown as { startViewTransition?: unknown }).startViewTransition;
});
it('reduced-motion:即使有 startViewTransition 也走降级(瞬切)', () => {
const startVT = vi.fn(() => ({ ready: Promise.resolve(), finished: Promise.resolve(), skipTransition: () => {} }));
Object.defineProperty(document, 'startViewTransition', {
value: startVT,
configurable: true,
writable: true,
});
vi.stubGlobal('matchMedia', vi.fn((q: string) => ({
matches: q.includes('reduce'),
media: q,
onchange: null,
addEventListener: () => {},
removeEventListener: () => {},
addListener: () => {},
removeListener: () => {},
dispatchEvent: () => false,
})));
window.__startThemeTransition(0, 0);
expect(startVT).not.toHaveBeenCalled();
expect(document.documentElement.classList.contains('dark')).toBe(true);
delete (document as unknown as { startViewTransition?: unknown }).startViewTransition;
});
});

View File

@ -0,0 +1,84 @@
/**
* (View Transitions API)
*
* CSS CSS
* 核心策略:始终让"暗色层", clip-path "亮色层"
* - -> : NEW (),(`tt-expand`) OLD
* - -> : OLD (),(`tt-shrink`) NEW
*
* WAAPI <style>,DOM
* API bug, VT
*/
function prefersReducedMotion(): boolean {
return (
!!window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
}
function maxCornerDistance(x: number, y: number): number {
const w = window.innerWidth;
const h = window.innerHeight;
const corners = [
[0, 0],
[w, 0],
[0, h],
[w, h],
];
let max = 0;
for (const [cx, cy] of corners) {
const d = Math.hypot(cx - x, cy - y);
if (d > max) max = d;
}
return max;
}
function applyDarkClass(isDark: boolean): void {
const html = document.documentElement;
if (isDark) {
html.classList.add('dark');
} else {
html.classList.remove('dark');
}
}
export function startThemeTransition(x: number, y: number): void {
const html = document.documentElement;
const isDark = !html.classList.contains('dark');
const hasVT = typeof document.startViewTransition === 'function';
const reduced = prefersReducedMotion();
if (!hasVT || reduced) {
applyDarkClass(isDark);
return;
}
const maxR = maxCornerDistance(x, y);
// 注入动画需要的 CSS 变量
html.style.setProperty('--tt-x', `${x}px`);
html.style.setProperty('--tt-y', `${y}px`);
html.style.setProperty('--tt-r', `${maxR}px`);
// 禁用所有 CSS transition,确保 VT 截图是最终颜色
html.classList.add('is-theme-transitioning');
const vt = document.startViewTransition(() => {
applyDarkClass(isDark);
// 强制同步样式重算:确保 body 的 background-color 解析为目标值
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
getComputedStyle(document.body).backgroundColor;
});
vt.ready.catch(() => {});
vt.finished.finally(() => {
html.classList.remove('is-theme-transitioning');
// 清理 CSS 变量
html.style.removeProperty('--tt-x');
html.style.removeProperty('--tt-y');
html.style.removeProperty('--tt-r');
});
}

1
libs/yggdrasil-core/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"skipLibCheck": true,
"esModuleInterop": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"types": []
},
"include": ["src"]
}

View File

@ -0,0 +1,23 @@
import { defineConfig } from 'vite';
import { resolve } from 'path';
export default defineConfig({
build: {
outDir: resolve(__dirname, '../../public/yggdrasil-core'),
emptyOutDir: true,
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'YggdrasilCore',
fileName: () => 'yggdrasil-core.js',
formats: ['iife'],
},
rolldownOptions: {
output: {
assetFileNames: 'yggdrasil-core.[ext]',
},
},
cssCodeSplit: false,
minify: true,
sourcemap: true,
},
});

View File

@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'happy-dom',
include: ['src/**/*.test.ts'],
},
});

View File

@ -1,48 +0,0 @@
(function () {
"use strict";
function initCopyButtons(root) {
var blocks = root.querySelectorAll("pre > code");
for (var i = 0; i < blocks.length; i++) {
var code = blocks[i];
var pre = code.parentElement;
if (!pre) continue;
if (pre.querySelector(".copy-code")) continue;
var btn = document.createElement("button");
btn.className = "copy-code";
btn.textContent = "copy";
btn.setAttribute("aria-label", "Copy code");
(function (codeText) {
btn.addEventListener("click", function () {
var self = this;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(codeText);
} else {
var ta = document.createElement("textarea");
ta.value = codeText;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
document.execCommand("copy");
document.body.removeChild(ta);
}
self.textContent = "copied!";
setTimeout(function () {
self.textContent = "copy";
}, 2000);
});
})(code.textContent || "");
pre.appendChild(btn);
}
}
window.__initPostContent = function (selector) {
var root = document.querySelector(selector);
if (!root) return;
initCopyButtons(root);
};
})();

View File

@ -14,7 +14,8 @@ use dioxus::router::components::Link;
#[derive(Props, Clone, PartialEq)] #[derive(Props, Clone, PartialEq)]
pub struct EmptyStateAction { pub struct EmptyStateAction {
/// 按钮文案。 /// 按钮文案。
pub label: &'static str, #[props(into)]
pub label: String,
/// 跳转目标路由。 /// 跳转目标路由。
pub to: crate::router::Route, pub to: crate::router::Route,
} }
@ -28,11 +29,14 @@ pub struct EmptyStateAction {
#[component] #[component]
pub fn EmptyState( pub fn EmptyState(
/// 主标题(通常为「还没有文章」之类)。 /// 主标题(通常为「还没有文章」之类)。
#[props(default)] title: Option<&'static str>, #[props(into, default = "还没有文章".to_string())]
title: String,
/// 副文案,说明当前状态或引导用户。 /// 副文案,说明当前状态或引导用户。
#[props(default)] description: Option<&'static str>, #[props(into, default = String::new())]
description: String,
/// 可选的行动按钮。 /// 可选的行动按钮。
#[props(default)] action: Option<EmptyStateAction>, #[props(default)]
action: Option<EmptyStateAction>,
) -> Element { ) -> Element {
rsx! { rsx! {
div { class: "flex flex-col items-center justify-center text-center py-20 px-4 page-enter", div { class: "flex flex-col items-center justify-center text-center py-20 px-4 page-enter",
@ -45,12 +49,12 @@ pub fn EmptyState(
} }
// 主标题:衬线字体,与首页 H1 风格呼应但更轻量。 // 主标题:衬线字体,与首页 H1 风格呼应但更轻量。
h2 { class: "mt-8 text-2xl font-bold tracking-tight text-paper-primary", h2 { class: "mt-8 text-2xl font-bold tracking-tight text-paper-primary",
{title.unwrap_or("还没有文章")} "{title}"
} }
// 副文案:次要色,限宽保证可读性。 // 副文案:次要色,限宽保证可读性。
if let Some(desc) = description { if !description.is_empty() {
p { class: "mt-3 text-sm leading-relaxed text-paper-secondary max-w-md", p { class: "mt-3 text-sm leading-relaxed text-paper-secondary max-w-md",
{desc} "{description}"
} }
} }
// 行动按钮:药丸形,与搜索页主按钮一致。 // 行动按钮:药丸形,与搜索页主按钮一致。
@ -58,7 +62,7 @@ pub fn EmptyState(
Link { Link {
class: "mt-8 inline-flex items-center px-6 py-2 bg-paper-accent text-white rounded-full font-medium text-sm hover:brightness-110 active:scale-[0.98] transition-all duration-200", class: "mt-8 inline-flex items-center px-6 py-2 bg-paper-accent text-white rounded-full font-medium text-sm hover:brightness-110 active:scale-[0.98] transition-all duration-200",
to: act.to, to: act.to,
{act.label} "{act.label}"
} }
} }
} }

View File

@ -10,14 +10,14 @@ use dioxus::prelude::*;
/// - `content_html`:服务端渲染的文章 HTML 字符串 /// - `content_html`:服务端渲染的文章 HTML 字符串
/// ///
/// 关键行为: /// 关键行为:
/// - 在 `target_arch = "wasm32"` 环境下执行 `post-content.js`(代码块复制)。 /// - 在 `target_arch = "wasm32"` 环境下调用 `window.__initPostContent` 初始化代码块
/// 复制按钮(`yggdrasil-core.js` 已由 `Dioxus.toml` 全局注入)。
/// 灯箱(图片灯箱 + 懒加载)改由 `Dioxus.toml` 全局注入 `lightbox.js` /// 灯箱(图片灯箱 + 懒加载)改由 `Dioxus.toml` 全局注入 `lightbox.js`
/// 这里仅设置其初始化配置 `__lightboxSelectors` 并兜底调用。 /// 这里仅设置其初始化配置 `__lightboxSelectors` 并兜底调用。
#[component] #[component]
pub fn PostContent(content_html: String) -> Element { pub fn PostContent(content_html: String) -> Element {
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use_effect(move || { use_effect(move || {
let _ = js_sys::eval(include_str!("../../../public/js/post-content.js"));
let _ = js_sys::eval("window.__initPostContent('.post-content')"); let _ = js_sys::eval("window.__initPostContent('.post-content')");
// lightbox 改由 Dioxus.toml 全局 <script src> 加载(不再 include_str!)。 // lightbox 改由 Dioxus.toml 全局 <script src> 加载(不再 include_str!)。
// 双保险契约:先设配置,若 lightbox.js 已加载则立即调用; // 双保险契约:先设配置,若 lightbox.js 已加载则立即调用;

View File

@ -391,23 +391,6 @@ fn main() {
"/readyz", "/readyz",
axum::routing::get(crate::api::health::readyz), axum::routing::get(crate::api::health::readyz),
) )
// /doc 与 /doc/ 重定向到文档入口 /doc/yggdrasil/index.html。
// public/doc/ 由 Dioxus 自动托管,深层路径无需额外处理;仅裸路径需要
// 重定向,否则会落到 Router 的 /:..segments 兜底返回 404 页面。
// 放在 static_routes与 /uploads 同层),不经过 CSRF/超时/缓存中间件,
// 行为与静态资源一致。用 301 永久重定向,让浏览器/书签记住跳转。
.route(
"/doc",
axum::routing::get(|| async {
axum::response::Redirect::permanent("/doc/yggdrasil/index.html")
}),
)
.route(
"/doc/",
axum::routing::get(|| async {
axum::response::Redirect::permanent("/doc/yggdrasil/index.html")
}),
)
.route( .route(
"/uploads/{*path}", "/uploads/{*path}",
axum::routing::get(crate::api::image::serve_image), axum::routing::get(crate::api::image::serve_image),

View File

@ -11,6 +11,7 @@ use dioxus::prelude::*;
use dioxus::router::components::Link; use dioxus::router::components::Link;
use crate::api::posts::{list_published_posts, PostListResponse}; use crate::api::posts::{list_published_posts, PostListResponse};
use crate::components::empty_state::EmptyState;
use crate::components::skeletons::archive_skeleton::ArchiveSkeleton; use crate::components::skeletons::archive_skeleton::ArchiveSkeleton;
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
use crate::models::post::PostListItem; use crate::models::post::PostListItem;
@ -119,17 +120,26 @@ fn ArchivesContent() -> Element {
let posts_data = posts_res.read(); let posts_data = posts_res.read();
match &*posts_data { match &*posts_data {
Some(Ok(PostListResponse { posts, total })) => { Some(Ok(PostListResponse { posts, total })) => {
let grouped = group_posts(posts); if *total == 0 {
rsx! { rsx! {
div { class: "mt-2 text-base text-paper-secondary", EmptyState {
"" title: "还没有文章归档",
span { class: "font-medium text-paper-primary", "{total}" } description: "发布文章后,这里会自动按年月进行归档显示。",
" 篇文章" }
} }
for year_group in grouped.iter() { } else {
YearSection { let grouped = group_posts(posts);
key: "{year_group.year}", rsx! {
year_group: year_group.clone(), div { class: "mt-2 text-base text-paper-secondary",
""
span { class: "font-medium text-paper-primary", "{total}" }
" 篇文章"
}
for year_group in grouped.iter() {
YearSection {
key: "{year_group.year}",
year_group: year_group.clone(),
}
} }
} }
} }

View File

@ -22,7 +22,7 @@ use crate::pages::post_detail::PostDetail;
use crate::pages::register::Register; use crate::pages::register::Register;
use crate::pages::search::Search; use crate::pages::search::Search;
use crate::pages::tags::{TagDetail, Tags}; use crate::pages::tags::{TagDetail, Tags};
use crate::theme::{use_theme_provider, Theme, ThemePreload}; use crate::theme::{use_theme_provider, ThemePreload};
/// 全站路由枚举,每个变体对应一个页面路径 /// 全站路由枚举,每个变体对应一个页面路径
#[derive(Clone, Routable, Debug, PartialEq)] #[derive(Clone, Routable, Debug, PartialEq)]
@ -106,12 +106,7 @@ pub enum Route {
/// 初始化主题提供者、全局用户上下文,并挂载样式表与 `Router`。 /// 初始化主题提供者、全局用户上下文,并挂载样式表与 `Router`。
#[component] #[component]
pub fn AppRouter() -> Element { pub fn AppRouter() -> Element {
// 获取当前主题以设置顶层 dark 类 let _theme = use_theme_provider();
let theme = use_theme_provider();
let theme_class = match theme() {
Theme::Dark => "dark",
Theme::Light => "",
};
// 提供全局用户上下文,供登录状态与路由守卫使用 // 提供全局用户上下文,供登录状态与路由守卫使用
let user = use_signal(|| None::<Arc<crate::models::user::PublicUser>>); let user = use_signal(|| None::<Arc<crate::models::user::PublicUser>>);
@ -122,7 +117,7 @@ pub fn AppRouter() -> Element {
document::Stylesheet { href: "/style.css" } document::Stylesheet { href: "/style.css" }
document::Stylesheet { href: "/highlight.css" } document::Stylesheet { href: "/highlight.css" }
document::Title { "Yggdrasil Blog" } document::Title { "Yggdrasil Blog" }
div { class: "{theme_class}", div {
ThemePreload {} ThemePreload {}
Router::<Route> {} Router::<Route> {}
} }

View File

@ -6,6 +6,11 @@
//! `prefers-color-scheme` 媒体查询;切换时同步更新 DOM class 与 localStorage。 //! `prefers-color-scheme` 媒体查询;切换时同步更新 DOM class 与 localStorage。
use dioxus::prelude::*; use dioxus::prelude::*;
// InteractionLocation 提供 client_coordinates(),用于读取鼠标点击的视口坐标。
// 该 trait 不在 dioxus::prelude(后者只 re-export events::*),需单独从 dioxus::html 引入。
// 仅 WASM 前端用到(ThemeToggle 的圆形展开动画取点击坐标),服务端构建剥离。
#[cfg(target_arch = "wasm32")]
use dioxus::html::InteractionLocation;
/// localStorage 中存储主题值的键名。 /// localStorage 中存储主题值的键名。
#[cfg(any(target_arch = "wasm32", test))] #[cfg(any(target_arch = "wasm32", test))]
@ -87,28 +92,19 @@ fn detect_initial_theme() -> Theme {
/// 提供主题上下文的 Hook。 /// 提供主题上下文的 Hook。
/// ///
/// 初始化时按 SSR Cookie → WASM localStorage → 系统偏好的顺序检测主题; /// 初始化时按 SSR Cookie → WASM localStorage → 系统偏好的顺序检测主题;
/// 主题变化时同步更新 HTML 根元素的 `dark` class 与 localStorage。 /// 主题变化时将值持久化到 localStorage。
///
/// `<html>` 的 `dark` class 不在此处管理。WASM 端由 `ThemeToggle` 的 onclick
/// 通过 `yggdrasil-core.js` 的圆形展开动画在 View Transition 回调里同步 toggle。
/// 初始 class 由 `ThemePreload` 首屏脚本设置,避免闪烁。
pub fn use_theme_provider() -> Signal<Theme> { pub fn use_theme_provider() -> Signal<Theme> {
let theme = use_signal(detect_initial_theme); let theme = use_signal(detect_initial_theme);
use_effect(move || { use_effect(move || {
let current = theme();
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
let current = theme();
if let Some(window) = web_sys::window() { if let Some(window) = web_sys::window() {
// 同步 HTML 根元素的 dark class用于 Tailwind dark mode。
if let Some(document) = window.document() {
if let Some(html) = document.document_element() {
match current {
Theme::Dark => {
let _ = html.class_list().add_1("dark");
}
Theme::Light => {
let _ = html.class_list().remove_1("dark");
}
}
}
}
// 将当前主题持久化到 localStorage。 // 将当前主题持久化到 localStorage。
if let Ok(Some(storage)) = window.local_storage() { if let Ok(Some(storage)) = window.local_storage() {
let theme_str = match current { let theme_str = match current {
@ -119,6 +115,8 @@ pub fn use_theme_provider() -> Signal<Theme> {
} }
} }
} }
// 避免 unused 警告:非 wasm 构建下 current 未被读取。
let _ = current;
}); });
use_context_provider(|| theme); use_context_provider(|| theme);
@ -155,16 +153,55 @@ pub fn ThemePreload() -> Element {
} }
/// 主题切换按钮组件。 /// 主题切换按钮组件。
// evt 仅在 wasm32 用于取点击坐标,服务端构建剥离,故允许非 wasm 的 unused_variables。
#[cfg_attr(not(target_arch = "wasm32"), allow(unused_variables))]
#[component] #[component]
pub fn ThemeToggle() -> Element { pub fn ThemeToggle() -> Element {
let mut theme = use_theme(); let mut theme = use_theme();
// generation:每次点击递增。延迟回调检查自己的 gen 是否最新,过期则跳过 set。
// 解决连续点击时多个 spawn_local 堆积导致的状态错乱。
#[allow(unused_mut)]
let mut click_gen = use_signal(|| 0u32);
rsx! { rsx! {
button { button {
class: "theme-toggle p-2 rounded-full cursor-pointer hover:text-paper-accent transition-colors duration-200 text-paper-secondary", class: "theme-toggle p-2 rounded-full cursor-pointer hover:text-paper-accent transition-colors duration-200 text-paper-secondary",
r#type: "button", r#type: "button",
aria_label: "切换深色/浅色主题", aria_label: "切换深色/浅色主题",
onclick: move |_| theme.set(theme().toggle()), onclick: move |evt| {
let next = theme().toggle();
#[cfg(target_arch = "wasm32")]
{
let coords = evt.client_coordinates();
let x = coords.x;
let y = coords.y;
// JS 从 DOM 现状推导目标主题(不传 isDark),避免与 Signal 状态不同步。
let _ = js_sys::eval(&format!(
"if (window.__startThemeTransition) \
window.__startThemeTransition({x}, {y});",
x = x,
y = y,
));
// theme.set 推迟到动画结束:其触发的 Dioxus 微任务重渲染会打断 VT。
// gen 确保连续点击只有最新回调 set。JS 已自治切换 dark class,
// 动画完成后再更新 Dioxus 状态,避免组件重渲染可能带来的干扰
let gen = click_gen() + 1;
click_gen.set(gen);
let mut theme_clone = theme;
let mut gen_clone = click_gen;
wasm_bindgen_futures::spawn_local(async move {
crate::utils::time::sleep_ms(450).await;
if gen_clone() == gen {
theme_clone.set(next);
}
});
return;
}
#[cfg(not(target_arch = "wasm32"))]
{
theme.set(next);
}
},
if theme() == Theme::Dark { if theme() == Theme::Dark {
svg { svg {
xmlns: "http://www.w3.org/2000/svg", xmlns: "http://www.w3.org/2000/svg",