db74a96de1
style: 格式化 Rust 与 TS 代码
2026-07-23 13:34:27 +08:00
bdfc7a19e9
feat(mermaid): 流程图主题切换跟随 VT 圆形扩散动画
...
此前 mermaid.render() 异步,而 VT 回调同步,Dioxus effect 重跑发生在 VT 回调
之后的微任务,导致动画期间流程图不变、动画结束后才瞬切。
利用 startViewTransition 回调可返回 Promise 的特性(浏览器等其 resolve 才拍
NEW 快照、播动画),让 VT 回调等 mermaid 重渲染完成,新主题流程图进入快照,
圆形扩散覆盖整页含 mermaid 区域。
改动:
- theme-transition.ts: 新增 onThemeChange registry(回调可返回 Promise)。
notifyThemeChange 遍历 registry 收集 Promise 返回聚合结果;THEME_CHANGE_EVENT
事件保留(给 codemirror/xterm 同步换肤,向后兼容)。
startThemeTransition 的 VT 回调改为 async,await notifyThemeChange 聚合 Promise,
确保异步换肤组件(mermaid)完成后再拍 NEW 快照。
applyResolvedTheme(跟随系统瞬切)不等 registry,保持瞬切语义。
- mermaid.ts: 顶层注册 onThemeChange 订阅,listener 调 rerenderExistingBlocks 并
返回其 Promise,供 VT 回调等待。initMermaid/rerenderExistingBlocks 返回 Promise。
- post_content.rs: VT 期间(is-theme-transitioning)跳过 __initMermaid 调用,避免
Dioxus effect 抢先重渲染穿透伪元素快照(照搬 code_runner/runner.rs 守卫);
动画结束后 effect 重跑做幂等兜底。
- theme-transition.test.ts: VT callback 签名 void→Promise<void>,新增 registry
等待测试 + 降级路径不等 registry 测试。
2026-07-16 16:58:27 +08:00
6eb7a743e5
fix(mermaid): 主题切换时重渲染已渲染的流程图
...
mermaid 把颜色烤进 SVG 内联样式,无法靠 CSS 原地切主题。此前 initMermaid
的幂等守卫一刀切跳过所有已渲染块,导致切主题后流程图停留在首次渲染的主题。
改动:
- initMermaid 改为双路径分发:未渲染块(<code>还在)走 IntersectionObserver
懒加载;已渲染块(pre 内容已是 SVG)按 dataset.mermaidSource 缓存源码重渲染,
同主题幂等跳过,主题变化才重渲染。
- 渲染逻辑抽到 renderBlock:用全局自增 renderCounter 生成唯一 id,避开 mermaid
残留的 d-前缀布局节点冲突(#357 )与同页 marker id 冲突(#5741 );替换前清空
pre.innerHTML 确保亮/暗两版 SVG 不并存。
- source 存进 dataset.mermaidSource 供重渲染回取(此时 <code> 已被 SVG 替换)。
触发机制不变:post_content.rs 的 use_effect 读 use_resolved_theme() 建订阅,
主题切换时重跑重调 __initMermaid 传入新 theme;mermaid.ts 据此决定是否重渲染。
2026-07-16 16:42:04 +08:00
45122fc687
fix(post): scrollToHash 增加一次性守卫,切主题不再跳回 URL hash
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
PostContent 的 use_effect 读取 resolved_theme() 建立订阅,主题切换时重跑
(为重跑 mermaid 主题)。但该 effect 同时调用 __scrollToHash,主题切换会
把页面跳回 URL hash 位置——即使该 hash 早已不在用户视野。
scrollToHash 是首屏异步取数后的补救滚动(弥补骨架屏阶段标题 DOM 缺失导致的
原生 fragment-scroll 失效),本质上是一次性的,不应随主题切换重跑。增加
did_scroll 信号守卫,仅首次 effect 运行时调用。
2026-07-16 16:22:42 +08:00
70edc5e46c
feat(frontend): mermaid 流程图懒加载渲染
...
mermaid 无官方 SSR 支持,采用客户端 IntersectionObserver 懒加载:服务端
markdown.rs 只产出普通 <pre><code class="language-mermaid"> 代码块,
前端在块进视口时动态 import 独立 bundle(~1MB)渲染成 SVG。
新增 libs/mermaid-renderer/:mermaid 11.16 IIFE bundle,输出 public/mermaid/
yggdrasil-core/mermaid.ts:扫描 language-mermaid 块,视口可见时动态 import
/mermaid/mermaid.js(单例缓存),mermaid.initialize 适配 light/dark 主题,
securityLevel=strict;渲染失败加 .mermaid-error class 保留源码;幂等守卫
post-content.ts:initCopyButtons 跳过 language-mermaid 块(图无需 copy)
post_content.rs:use_effect 调 __initMermaid('.post-content', theme),
读 use_resolved_theme() 传主题并建立订阅
新增 6 个 mermaid 单测(扫描/主题/幂等/非mermaid/未命中/错误回退)。
550 Rust + 28 前端测试全过,双 target 编译通过。
2026-07-16 14:22:43 +08:00
886eeca915
style: 全项目 rsx! 格式规范化(cargo fmt)
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
2026-07-15 17:49:03 +08:00
65a7b1226f
style: 全项目格式规范化(cargo fmt)+ Docker daemon 断连容错
...
- 全项目 .rs 统一 cargo fmt 排版(换行/缩进/参数分行/导入排序)
- infra/docker: DOCKER_CLIENT 连接失败不 panic,降级返回 None
(博客不依赖 Docker,panic=abort 下会导致整个进程崩溃)
- infra/mod: 去除多余空行
- database/mod: 模块声明按字母序重新排序
2026-07-15 17:22:06 +08:00
5ad6ce60cd
fix(post): hydration 后点击标题锚点触发整页刷新
...
WASM 下载完成后点击 TOC/标题 # 锚点会整页刷新且不滚动,而 SSR 阶段(WASM
未就绪)点击正常——这是 Dioxus 0.7 事件委托的副作用:
项目任意位置用 onclick(post_nav_links.rs)即触发 Dioxus 全局 click 委托。
点击 dangerous_inner_html 注入的 <a href="#id"> 时,事件冒泡到根监听器,
handleEvent 发现 <a> 无 Dioxus onclick → 走兜底 handleClickNavigate:
preventDefault 拦掉原生 fragment-scroll,再 browser_open IPC 把 hash 当
外部 URL 整页加载(History::external → location.set_href)。
修复:yggdrasil-core 新增 anchor-click.ts,在 window capture 阶段(早于
Dioxus 的 bubble 委托)拦截同页 hash 锚点点击,stopPropagation 阻止事件
到达 Dioxus,自行 scrollIntoView + replaceState 更新地址栏。中键/修饰键、
外链、目标不存在等情况放行原行为。
81682eb 的 scrollToHash 只在 PostContent 挂载时跑一次,不覆盖点击场景;
本修复与之正交,共同覆盖刷新与点击两条路径。initAnchorClick 幂等。
2026-07-13 18:22:53 +08:00
81682eb70e
fix(post): 修复 hash 锚点跳转失效
...
内容异步加载(use_server_future)导致骨架屏阶段标题 DOM 缺失,浏览器原生
fragment-scroll 找不到目标留在顶部;刷新页面也因 SSR 出的是骨架屏而失效。
- 新增 __scrollToHash 全局入口(yggdrasil-core):内容挂载后解码
location.hash(CJK 百分号编码 → 原始字符)并 scrollIntoView
- PostContent use_effect 末尾复用 invoke_optional_global 调用,标题
DOM 已就绪时补一次滚动
- 标题加 scroll-margin-top: 6rem,避开 80px sticky header
点击 TOC/标题 hashtag 由原生 anchor + scroll-margin-top 覆盖,无需 JS。
2026-07-10 00:21:08 +08:00
f86cb48efe
fix(post): 上下篇切换后正文内容不更新
...
承接 fix(post) 的路由重跑修复:标题/描述更新后,正文仍停留在旧文章。
根因:PostContent 用 use_memo 缓存 split_content_fragments 的结果,但 memo
依赖 ReactiveContext 追踪闭包内读取的 signal 才会重算。content_html 是普通
String prop,读取它不建立订阅——memo 永久缓存首次解析结果。上下篇切换时
content_html prop 已是新文章的 HTML,但 memo 返回旧 fragments,dangerous_inner_html
收到旧 html 字符串,diff 判断属性值未变 → 不生成 setAttribute 编辑 → 正文 DOM
停在旧文章。
标题/描述之所以正常更新:它们直接读 post(来自 use_server_future 的 Resource,
是 signal),不走 memo。
修复:去掉 use_memo,直接在 render 内调用 split_content_fragments(纯函数,
符合渲染纯净性)。每次渲染用当前 content_html 重新解析,diff 收到新 html →
正文 DOM 正确更新。
验证(headless chromium):
- 修复前:/post/rust 点 Next,标题变「写文章页面改为左右布局」,正文仍是
rust 的「第一步:定义世界」(红灯,复现)
- 修复后:正文随之变为「问题」(绿灯);prev 方向同样通过;首次 SSR 正常
2026-07-09 16:39:41 +08:00
b95777e58d
fix(editor): 修复 SSR 直接访问时可运行代码块不挂载 CodeMirror
...
CodeRunner 原用 use_hook(|| format!("code-runner-{}", now_pseudo_unique()))
生成容器 id,后缀依赖 now_millis() 时间戳。Dioxus hydration 不传递
use_hook 状态——SSR 渲染与客户端 hydration 各执行一次 use_hook,时间戳
(以及顺带验证过的 ScopeId)两端不同,导致 CodeMirror create() 在 hydration
时用新 id 去 getElementById,找不到 SSR 渲染的容器(id 是旧值),返回 null
不挂载。路由跳转是纯客户端渲染,use_hook 只跑一次 id 一致,故正常。
改为由父组件传入确定性的 instance_id(PostContent 的片段索引 i,来自纯函数
split_content_fragments 对同一 content_html 的解析,SSR 与 hydration 一致),
container_id 直接派生自该 prop,不进 use_hook。admin 试运行页单实例固定 0。
Playwright(release 构建)验证:SSR 直接访问与路由跳转 .cm-editor 均为 1。
2026-07-08 11:04:40 +08:00
0e8b5fd7b7
feat(ui): scan and mount code-runner component client-side in reader
...
采用 Dioxus vdom 友好的片段拆分(避免篡改 dangerous_inner_html 产物导致 hydration 冲突):
- post_content.rs: split_content_fragments 把 content_html 拆成 Html/Runnable 片段,
Runnable 作为 <CodeRunner> 组件穿插渲染;HTML 实体解码还原源码;6 个单测
- markdown.rs: 可运行 pre 增加 data-source(转义后的原始源码), 供阅读器无损提取
- sanitizer.rs: 放行 pre 的 data-source 属性
(任务 7 的 data-source 增量一并在此提交)
2026-07-03 18:35:57 +08:00
fee5b7ba94
refactor(wasm): 消除 DOM 互操作中的 js_sys::eval 字符串求值
...
对照 Dioxus "Breaking Out" 文档审查后,将 3 处 js_sys::eval 改为
类型化调用,与 tiptap_bridge/codemirror_bridge 风格统一:
- utils/time.rs sleep_ms:eval("new Promise(r=>setTimeout(r,n))")
改为 js_sys::Promise + web_sys::Window::set_timeout_*,复用项目内
system.rs::wasm_sleep 同款写法。
- theme.rs 主题切换:format!-into-eval 拼 __startThemeTransition(x,y)
改为 Reflect::get 取函数 + Function::call2,消除字符串注入面
(原坐标虽为 f64 无实际风险,但属不必要 eval)。
- post_content.rs:__initPostContent / __initLightbox 两处 eval 字面量
改为 invoke_optional_global 辅助函数(Reflect::get + Function::apply),
消除旧版 "eval 字符串粘贴" 模式;__lightboxSelectors 直接复用 JsValue
传入,省去一次回读。
2026-07-02 17:15:18 +08:00
373498870a
style: apply cargo fmt to workspace
CI / check (push) Failing after 5m35s
CI / build (push) Has been skipped
2026-06-29 13:47:40 +08:00
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
0398cc6c66
perf(render): 列表加显式 key + release 启用 panic=abort
...
依据 Dioxus 0.7 optimizing 指南做两项改进:
- 为数据驱动列表渲染补全显式 key(13 处,9 个文件):
home/archives/tags/search 文章与标签列表、post_card/post_footer
标签、header 桌面/移动导航、admin dashboard/posts/write 列表。
使用各类型稳定唯一字段(post.id / tag / tag.name / item.label /
err.id / year_group.year / month_group.month_en)作为 key,
让 Dioxus diff 正确识别元素身份。骨架屏 for _ in 0..N 静态占位
循环与已带 key 的评论树/admin 行不动。
- [profile.release] 加 panic = "abort":WASM 去掉 unwind 元数据减小
体积;server 端错误处理走 Result+?(见 error.rs),不依赖 panic
unwind,无副作用。
其余文件为本机格式化(单行折叠/多行展开),一并提交。
验证:dx check 通过、cargo test 405 passed。
2026-06-25 18:07:22 +08:00
0301185a17
refactor(post-content): drop lightbox include_str!, use config-driven init
2026-06-24 11:07:24 +08:00
75b9080df8
refactor(post-cover): use raw blur-img structure as single lightbox image
...
drop ImageViewer; render .blur-img with a lightbox-single class (the
class, not a data attribute, because Dioxus' typed span elements reject
arbitrary data-* attrs). lightbox.js now keys single-mode off the
lightbox-single class instead of data-single. cover uses ?w=20
placeholder + ?w=1200 display; SSR writes --ar from real dimensions.
2026-06-23 15:48:02 +08:00
f88d83b0ad
feat(post-content): load lightbox.js and init gallery
...
eval both post-content.js (copy) and lightbox.js (lightbox + lazy-load),
then call __initLightbox over ['.post-content', '.entry-cover'] so both
in-content images (gallery) and the cover (single) are wired up.
2026-06-23 15:45:30 +08:00
c43da3676f
docs(components): 补充中文注释
2026-06-12 19:42:42 +08:00
44c1358da5
refactor: extract post content JS into standalone script
2026-06-09 13:18:40 +08:00
0345fa70f2
fix: add click handler for code block copy button
2026-06-09 11:33:26 +08:00
42b39266a6
fix: preserve data URI in img src during markdown rendering
...
ammonia's default url_schemes whitelist excludes 'data', causing
data:image/* URLs to be stripped from img src attributes. Add 'data'
to allowed schemes. Also skip data: URIs in JS thumbnail logic.
2026-06-09 10:06:34 +08:00
b6f41e74e7
feat: image thumbnail + lightbox viewer
...
- Add ImageViewer reusable component with thumbnail and click-to-zoom
- PostCover: load ?w=1200 thumbnail, click to view full-size
- PostCard: display cover thumbnail (?thumb=400x300) in list view
- PostContent: inline images load ?w=800 thumbnail, click to zoom
- Add lightbox overlay styles with fade-in animation
- Add zoom cursor and hover effect for zoomable images
- Extend web-sys features for DOM image/lightbox manipulation
2026-06-08 15:52:47 +08:00
6b1f2e27c9
feat(components): add post page components (header, toc, content, footer, nav)
2026-06-02 18:21:25 +08:00