c6999763c0
fix(editor): 修复 tiptap-markdown 过度转义导致显示损坏
...
@tiptap/markdown 的 escapeMarkdownSyntax 对每个非代码文本节点的 */_/>
无差别转义,导致 content_html→content\_html、>→>、字面星号被反复
转义累积,前台表格/加粗/引用块显示为裸 markdown 文本。转义幂等且自增强
(已转义的 \* 被 marked 解析为 escape token→再转义→粘住),只有源码模式
保存原文才能清除。
pnpm patch 持久化修补上游包(提交进 libs/patches/,pnpm install 自动应用):
- escapeMarkdownSyntax 改为 CommonMark 风向感知:只在真正构成强调开/闭
边界的位置转义 */_(一侧 space/punct、另一侧 other),词内位置不转义
- encodeHtmlEntities 降级为 encodeLimitedHtmlEntities:只编码 < 和 &,
移除 > (文本节点内的 > 不构成 blockquote,编码只会产生 > 损坏)
- 同步修补 src/dist ESM/dist CJS 三处
- 8 个往返单测覆盖词内下划线/行内>/字面星号/真实强调/多轮稳定
2026-07-23 12:04:25 +08:00
2b75ac4d28
fix(editor): 修复 tiptap 序列化转义脚注语法 [^id] → \[^id\] 导致失效
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
@tiptap/markdown 的 escapeMarkdownSyntax (MarkdownManager.ts:1112) 对所有
非代码文本节点做 [→\[、]→\] 转义,导致脚注引用/定义 [^id] 被破坏为 \[^id\],
pulldown-cmark 将其视为字面文本而非脚注语法,渲染出纯文本而非上标链接。
与数学公式节点同类问题(LaTeX 通过 atom Node + renderMarkdown 绕过),
但脚注是普通文本节点无法走相同路径,故在 getMarkdown 输出后做后处理:
将 \[^label\] 还原为 [^label],仅匹配脚注专属模式不影响普通链接语法。
同时修复数据库中已损坏的 markdown-syntax-test 和 markdown-quan-te-xing-
ce-shi-wen-zhang 两篇文章的 content_md,并通过 rebuild 全量重建 content_html。
2026-07-23 11:10:21 +08:00
864c813c6d
feat(code-runner): 高亮层补 bun 别名、CodeMirror 加 TS 模式
...
syntect 高亮别名表(highlight.rs)加 ('bun','ts'):bun 运行器跑的是
TypeScript,归一化后用 ts 扩展名命中 TypeScript.sublime-syntax。
CodeMirror buildLanguageExtension(codemirror-editor/editor.ts)新增
bun/typescript/ts 分支 → javascript({ typescript: true }),让 bun 代码块
在阅读器侧编辑器获得 TS 类型感知高亮。归一化后 CodeRunner 实际只传
canonical key,但保留别名分支作防御(CodeMirror 也在 SQL 控制台等场景用)。
两边都补了测试:highlight_code_resolves_bun_alias_to_typescript 锁定
'bun' 与 'ts' 输出一致;CodeMirror 经 tsc --noEmit + vite build 通过。
2026-07-21 15:23:47 +08:00
63a52f62c9
chore(format): mermaid-renderer 入口文件对齐 biome 格式
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
biome format 清理:去掉赋值语句的多余前导分号与空行。
该文件此前未走过 biome format(前导 ; 与 semicolons:always 配置无关,
仅为历史写法)。无行为变化。
2026-07-17 17:41:39 +08:00
5dceb39544
feat(editor): 数学公式节点带 KaTeX 预览,根治编辑器序列化破坏 LaTeX
...
问题:经 Tiptap 编辑器保存的数学公式,矩阵/分段函数等服务端 KaTeX 渲染失败。
根因:@tiptap/markdown 的 MarkdownManager.encodeTextForMarkdown 对所有非代码
文本节点做 escapeMarkdownSyntax + encodeHtmlEntities,把 LaTeX 的 \→\\、
_→\_、&→&。编辑器内公式走普通文本路径,经 getMarkdown() 序列化后存库即损坏。
解法:把公式建模成 atom Node(InlineMath/DisplayMath),LaTeX 存 attrs.latex
而非子文本;声明 markdownTokenizer/parseMarkdown/renderMarkdown 顶层 spec 字段,
序列化时 renderMarkdown 直接拼 $...$/$$...$$,绕过 encodeTextForMarkdown
转义路径(MarkdownManager 有 handler 的非文本节点不走 escape)。
NodeView 内用客户端 katex.renderToString 渲染预览(类 Notion/Typora),双击编辑
LaTeX 源码。KaTeX CSS 已由 Dioxus.toml 全局注入,无需额外接线。
- 新增 katex ^0.16.22 依赖(workspace 已 hoisted)
- 11 个回归测试覆盖矩阵/分段函数/round-trip 序列化往返
2026-07-17 17:33:37 +08:00
156b51d697
feat(highlight): 文章页代码块支持 Vue SFC 语法高亮
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
阅读侧(syntect)此前对 ```vue 回退纯文本:syntect 默认集无 Vue
语法、syntaxes/ 无定义、别名表无 vue 条目。
官方 Vue Component.sublime-syntax 因 extends: Packages/HTML/...
用 Sublime Text 包路径,syntect 的 add_from_folder 解析不了(实测
panic),无法直接采用。改为自包含实现:
- syntaxes/Vue.sublime-syntax:自包含 Vue SFC 语法。<template>/
<script>/<style> 三段在 main 入口按标签名分流(syntect context
切换后无法回看标签名)。script 按 lang 属性经前瞻分流
ts/tsx→source.ts、其余→source.js;style 统一 embed source.css
(scss/less 无对应 scope,因 CSS 是超集兼容);template 内 {{ }}
插值 embed source.js,其余走内置 HTML 语法。
- 关键约束(syntect 5.3 实测):embed 规则必须用纯净 match+embed+
escape 写法,附加 pop/embed_scope 会破坏 embed 使其静默回退纯文本;
escape 用 lookahead 不消费闭合标签。
编辑器侧(lowlight@3.3.0)与阅读侧是独立链路。lowlight 依赖的
highlight.js 11 早已移除 vue grammar,common/all 均不含;
highlightjs-vue 包停在 2019 年 1.0.0 且用旧版 hljsDefineVue 全局
API,与 lowlight@3 的 ESM register 不兼容。故编辑器侧用 registerAlias
把 vue 注册为 xml 别名兜底(template 段按 XML 着色,script/style 段
不精确但不抛 Unknown language);读者看到的阅读侧仍是完整高亮。
src/highlight.rs 别名表加 vue 条目(兜底大写 ```Vue 边界),新增 3 个
Vue 高亮单测(SFC 三段 / lang=ts 走 TS / 大小写别名)。
2026-07-17 11:42:55 +08:00
fac247f60a
feat(mermaid): 流程图配色对齐 Catppuccin + 容器美化
...
mermaid.initialize 之前用内置 'default'/'dark' 主题,配色与站点 Catppuccin
调色板完全脱节,图是"外来"的样子;且零 mermaid 专用 CSS,SVG 直接套在通用
pre 容器里,宽图横向滚动、无居中无留白。
改动:
- mermaid.ts:theme 切到 'base'(base 不硬编码颜色,themeVariables 能完全
控制调色板;'default' 主题硬编码 mainBkg=#ECECFF 会阻断覆盖)。新增 Latte/
Mocha 两套 themeVariables,节点用 surface 色阶、边框/连线用 subtext 色阶、
文字用 primary text,hex 取自 themes/*.tmTheme。flowchart 配 basis 平滑曲线
+ diagramPadding 16 + useMaxWidth:true(配合 CSS 消除横向滚动)。
- input.css:新增 .md-content pre[data-mermaid-rendered] 规则,flex 居中 + svg
max-width:100% 缩放适配;.mermaid-error 加 accent 色左边条提示。
- mermaid.test.ts:theme 断言同步到 'base'+darkMode;新增 themeVariables
注入测试(钉住 Latte/Mocha 关键 hex)。
范围:纯前端 TS + CSS,零 Rust 改动。
2026-07-17 10:13:20 +08:00
0395c55213
fix(mermaid): 修复 tsc 类型错误
...
- mermaid.ts: initMermaid 早退路径(root 未命中)返回 Promise.resolve() 而非
undefined,匹配 Promise<void> 签名。
- theme-transition.test.ts: resolveAsync 改用 mutable 容器 { fn: null } 包裹,
绕过 TS 跨闭包控制流收窄(原 let + 闭包赋值,在 ?.() 处被判定为 null → never)。
上提交漏跑 tsc --noEmit(vitest 用 esbuild 不做类型检查),本次补验 tsc + vite build。
2026-07-16 17:06:06 +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
f6b7ab4333
fix(post): scrollToHash 增加 ResizeObserver 布局稳定期,修正 mermaid 异步渲染导致的锚点落点偏移
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
直接访问 /post/slug#heading 或刷新该 URL 时,目标标题停在屏幕中间偏下,但点击目录
或标题跳转却正常。根因是时序竞争:
post_content 的 use_effect 同步依次调用 __initMermaid(注册 IntersectionObserver,
异步)→ __scrollToHash。scrollToHash 的"立即 + 两帧"校正只覆盖浏览器原生
fragment-scroll;而 mermaid 懒加载(加载 ~1MB bundle → render → pre.innerHTML=svg)
是数百毫秒的异步链,发生在两帧之后。mermaid 块从源码文本膨胀成 SVG 图时撑高内容,
把目标标题推离已校准的落点。mermaid.ts 渲染完成后无任何落点修正。点击目录正常是
因为那时 mermaid 已渲染、布局稳定。
解法:scrollToHash 末尾新增 stabilizeScrollOnResize——在 STABILIZE_WINDOW_MS(2s) 内
用 ResizeObserver 观察 .post-content 容器,检测到布局变化即用 rAF 合并重新校正一次
落点。与 mermaid 解耦,同时覆盖图片/字体等所有异步位移源。用户主动滚动(wheel/
touchmove/keydown)或超时后自动停止,不与交互打架。重入安全:主题切换重跑 use_effect
时先 dispose 上一次稳定器,避免泄漏/叠加。
测试(hash-scroll.test.ts)新增 3 例:resize 后再校正、用户 wheel 后停止、重入清理旧 RO。
2026-07-16 16:05:11 +08:00
445b92f407
fix(mermaid): 改用 script 标签加载 IIFE bundle,修正全局变量取值
...
根因:mermaid bundle 是 IIFE 格式(与项目其他前端库一致),挂全局变量
而非 ES module export。但 mermaid.ts 错用了 import() 取 .default——
IIFE 无 export,mod.default 是 undefined,导致 mermaid.initialize 调用
TypeError,流程图永远不渲染(数学公式不受影响,因为是服务端渲染)。
修复:
- mermaid.ts: loadMermaidBundle 改用动态注入 <script src=/mermaid/mermaid.js>
标签,加载后从 window.MermaidRenderer 取(与 codemirror/lightbox 的
Reflect::get(window,...) 模式一致)
- mermaid-renderer/index.ts: 显式 globalThis.MermaidRenderer = mermaid,
避免 vite IIFE 把 export default 编译成 .default 多一层
- markdown.rs: mermaid 代码块跳过 syntect 高亮(无语法定义且会包 <span>
污染 textContent),直接输出转义纯源码
前端 28 测试 + Rust 557 测试 + 双 target 编译通过。
2026-07-16 15:36:07 +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
218533402b
feat(css): 自托管 KaTeX CSS 与 woff2 字体
...
服务端 katex-rs 只输出 HTML span,字体/排版靠配套 CSS。从 npm 包 katex
0.16.x 的 dist/ 拷贝 katex.min.css + woff2 字体(省去 woff/ttf)到 public/katex/,
CSS 用相对 url(fonts/) 引用故两者须同级。
- libs/package.json: katex ^0.16.22 作 workspace 根 devDependency
- Makefile: 新增 katex-css target,接入 build/build-linux/dev;clean 清 public/katex
- Dioxus.toml: [web.resource]/[web.resource.dev] style 列表注册 /katex/katex.min.css
- .gitignore: public/katex 作构建产物忽略(同 highlight.css 等)
make katex-css 验证通过:23KB CSS + 20 个 woff2 字体就位。
2026-07-16 14:15:20 +08:00
1935321da1
build(deps): 升级 TypeScript 至 7.0.2(Go 原生编译器)
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
TypeScript 7 已将 Go 重写版(代号 Corsa)合并进主线 typescript 包,
npm latest 即 7.0.2,无需再依赖 @typescript/native-preview 预览包。
本项目单点改动:libs/package.json devDependencies.typescript ^6.0.3 -> ^7.0.2
(6 个子包均从 workspace 根继承版本,无需逐包改动)。
兼容性核对(TS 7 破坏性变更 vs tsconfig.base.json):
- target ES2020 / module ESNext / moduleResolution bundler 均在支持范围
- 未使用 baseUrl / node10 / es5 等 TS 7 移除的选项
- esModuleInterop=true / strict=true 满足 TS 7 的新下限要求
- css 副作用导入已有 css.d.ts 声明,满足 noUncheckedSideEffectImports 新默认值
验证:typecheck(6 包 tsc --noEmit) + build(6 包 tsc && vite build)
+ test(187 个 vitest 用例)全部通过。
2026-07-16 13:36:54 +08:00
22d05a98b3
chore(lint): 修复 Biome 告警(schema 版本/optional chain/CSS !important 抑制)
...
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
- biome.json: $schema 2.5.1 → 2.5.3,对齐已安装的 CLI 版本(消除 info)
- code-block-view.ts:157: `a && a.b()` → `a?.b()`(等价 optional chain)
- style.css:56: 保留刻意的 `transform: none !important`(删除会重新引入
animation-fill-mode: both 导致的堆叠上下文 bug),仅加 scoped biome-ignore
抑制 noImportantStyles,不做全局禁用
2026-07-15 18:12:04 +08:00
9bc8b5c2ee
refactor(libs): 抽取 @yggdrasil/shared 消除跨 IIFE 库的类型/常量重复
...
新增 libs/shared 内部包,作为跨 IIFE 库的单一真相源:
- ThemeName 类型(原 codemirror/themes.ts + xterm/themes.ts 各一份)
- THEME_CHANGE_EVENT 常量(原 yggdrasil-core 导出 + codemirror/xterm
各硬编码同名 string literal,靠注释维系一致)
- prefersReducedMotion 函数(原 lightbox + yggdrasil-core 各一份)
codemirror-editor / xterm-terminal / lightbox / yggdrasil-core 四个库
改为 workspace 依赖 @yggdrasil/shared,Vite 构建时 inline 进各自 IIFE,
不破坏 IIFE 隔离。消除「改一处忘改另一处导致静默失效」的风险。
附:image_reader_limits 已在上一提交共享;本提交是 libs 侧的对称改动。
2026-07-14 15:39:41 +08:00
067075df8a
fix(post): 直接访问 #hash 时标题被 sticky header 遮住
...
点击锚点正常,但直接访问带 hashtag 的链接时滚动后标题被 80px sticky
header 遮住。根因:浏览器原生 fragment-scroll(直接访问触发)不读 CSS
scroll-margin-top,在我们 scrollToHash 的平滑滚动之后触发,把标题顶到
视口最顶端(header 下方)。
抽出共享 scrollToHeading:用 getBoundingClientRect 手动计算绝对滚动位置,
减去动态测量的 header 高度(+16px 呼吸空间)后 window.scrollTo,不再依赖
scroll-margin-top。hash-scroll(直接访问)与 anchor-click(点击)统一调用它。
scrollToHash 额外用双 requestAnimationFrame 延后做一次即时校正,覆盖原生
fragment-scroll 的触发窗口,确保落点在原生 scroll 之后生效。
scroll-margin-top: 6rem 保留在 input.css 作其它原生 scroll 场景的兜底。
2026-07-13 18:31:31 +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
f984c0c3e7
fix(theme): clear animate-page-enter transform during VT to fix code block coverage
...
主题切换的圆形展开 VT 动画对可运行代码块不生效:代码块直接瞬切,而非被
圆形展开揭示。上一个提交(theme-change 事件同步 setTheme)只解决了快照时序
问题,但真实环境仍不生效。
根因(真实页面逐帧截图 + 像素采样确认):article.post-single 的 animate-page-enter
入场动画用 transform: translateY/scale,因 animation-fill-mode: both 在动画结束后
保留计算值。transform: none 被浏览器序列化为 matrix(1,0,0,1,0,0),仍是「非 none」
的 transform,创建堆叠上下文(stack context)。::view-transition-old/new(root) 顶层
伪元素无法正确覆盖堆叠上下文内部的 CodeMirror/xterm——圆形展开扫过代码块区域时,
伪元素被穿透,代码块的实时 DOM 变化(use_effect 驱动的 setTheme)直接可见,表现为
「直接瞬切,不受 VT 动画控制」。
修复:在 is-theme-transitioning 期间用 !important 清除 .animate-page-enter 的
transform。is-theme-transitioning 在 startViewTransition 之前添加,OLD/NEW 快照
均含此 class,故两快照 transform 都被清为 none,一致,不影响动画效果。
验证:
- scripts/vt-real-page-sampler.mjs + vt-xterm-real-sampler.mjs:Playwright 驱动
真实 dx serve 页面,逐帧截图 + 像素采样
- 修复前:代码块在圆形边界外已变 dark(直接瞬切);修复后:代码块随圆形展开
同步变色(lag 与几何距离一致,非瞬切)
- 像素追踪:editor 在 t=266ms 变色(圆形到达),bg 在 t=362ms 变色(圆形到达更远点),
时序与圆形几何一致,证明两者都通过 VT 动画变色
2026-07-13 12:02:30 +08:00
bbb77954e9
fix(theme): sync CodeMirror/xterm theme in VT callback for circular-expand animation
...
主题切换的圆形展开 VT 动画对可运行代码块不生效:圆形扫过 CodeMirror/xterm
区域时看不到颜色变化,动画结束后才瞬切。
根因(逐帧像素验证确认):VT 回调只 toggle .dark class,但 CodeMirror 背景
由 catppuccin Extension 注入(.cm-editor 的 EditorView.theme 规则),xterm
背景由 .xterm-scrollable-element 的 inline background-color 注入——两者都
不随 .dark 翻转。set_theme 由 Dioxus use_effect 在 theme.set() 之后异步触发,
晚于 NEW 快照捕获,导致 OLD/NEW 快照在编辑器区域同色,圆形展开无可揭示的变化。
修复:在 VT 回调内(NEW 快照捕获前)同步 dispatch 'yggdrasil:theme-change'
CustomEvent,CodeMirror/xterm 各自的 index.ts 在模块加载时订阅并遍历
_instances 同步调 setTheme。事件先于 applyDarkClass dispatch,确保编辑器换肤
+ class 翻转被同一个 getComputedStyle reflow 捕获进 NEW 快照。
与现有 Dioxus use_effect 幂等共存:setTheme 对相同主题是 no-op(CodeMirror
Compartment.reconfigure + xterm options.theme = 均幂等),use_effect 作兜底
覆盖初始挂载 / 非 VT 场景。
验证:
- yggdrasil-core 测试扩展:断言 VT 回调 / 降级路径 / applyResolvedTheme 都
dispatch 事件,且事件先于 dark class 翻转
- scripts/vt-theme-sampler.mjs(Playwright 逐帧像素采样):修复前 xterm 比
等距的 cssvar 探针晚 100ms 变色(bug);修复后同帧变色(lag=0ms)
改动文件:
- libs/yggdrasil-core/src/theme-transition.ts: THEME_CHANGE_EVENT 常量 +
notifyThemeChange 辅助函数,VT 回调 / 降级路径 / applyResolvedTheme 三处 dispatch
- libs/codemirror-editor/src/index.ts: 订阅事件,forEach _instances.setTheme
- libs/xterm-terminal/src/index.ts: 同样模式
- libs/yggdrasil-core/src/theme-transition.test.ts: 3 个新用例
- public/xterm/terminal.js: 重建产物(含事件订阅)
- scripts/vt-theme-{harness,sampler}: 逐帧像素验证工具(throwaway,可作回归)
2026-07-13 11:20:08 +08:00
e347776dcd
fix(ui): align xterm terminal background with --color-paper-code-block
...
xterm.js terminal background was Catppuccin Base (#eff1f5 / #1e1e2e),
but the project's code-block background CSS variable uses Catppuccin
Surface0 (#dce0e8 light / #313244 dark). The mismatch left the output
area visibly lighter (light mode) / darker (dark mode) than the
surrounding container.
2026-07-13 09:43:01 +08:00
6f4385221a
test(libs): add xterm-terminal smoke tests
...
4 tests covering XtermOptions construction/setters and TerminalInstance
mount/onReady/writeAll/clear/destroy lifecycle under happy-dom.
2026-07-10 11:59:38 +08:00
246d8a6d67
feat(libs): add xterm-terminal IIFE library (xterm.js 6.0)
...
New pnpm workspace package @yggdrasil/xterm-terminal, output to
public/xterm/. Output-only terminal (disableStdin) for streaming
container stdout/stderr via SSE.
- @xterm/xterm@^6.0.0 + @xterm/addon-fit@^0.11.0 (released same day,
2025-12-22; 0.10.0's peerDependency is ^5.0.0 and incompatible)
- convertEol: containers emit \n, xterm needs \r\n
- stderr wrapped in ANSI red for visual distinction
- Catppuccin Latte/Mocha themes matching project highlight.css
- Mirrors codemirror-editor IIFE pattern: object literal default export,
XtermOptions as class (survives TS erasure for wasm-bindgen)
- css.d.ts declares *.css module for tsc side-effect import
2026-07-10 11:37:04 +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
1584e01425
chore(deps): update all cargo and pnpm dependencies to latest versions
2026-07-09 18:12:54 +08:00
1521414cad
fix(code-runner): 修复编辑器内运行结果不换行
...
resultArea 挂在 ProseMirror 可编辑文档树内部,浏览器对 contenteditable
子树强制 white-space 折叠(computed 为 collapse),CSS 的 white-space:
pre-wrap 被覆盖,导致 textContent 里的 \n 与行首空格被吞、多行输出挤成一行。
改用 <br> 显式渲染换行、\u00A0 替换空格/制表符以保留对齐——二者均不依赖
white-space。createTextNode 避免 stdout 含 HTML 标签时的注入。
2026-07-09 13:25:59 +08:00
cda5a92230
feat(runner): enable Vim mode toggle for CodeRunner sandbox
2026-07-08 14:37:52 +08:00
5b9e65bf72
fix(editor): 修复文章页折叠行被撑高 & 折叠图标垂直不居中
...
文章页 CodeRunner 嵌在 .md-content 内,CodeMirror 折叠后插入的
<img class="cm-widgetBuffer">(width:0、height:1em 的占位符,用于对齐折叠
widget 基线)被 .md-content img 的 margin:1rem 0 命中,上下各 16px margin
把折叠那行 .cm-line 撑到 47.5px(试运行页不在 .md-content 内,正常 19.6px)。
对比两页 .cm-line 的 children 锁定:文章页含 cm-widgetBuffer,试运行页没有。
input.css:.md-content img 选择器加 :where(:not(.cm-widgetBuffer)) 排除占位 img,
用 :where() 压低特异性避免影响其它规则。
themes.ts:core 对折叠列 .cm-foldGutter .cm-gutterElement 完全没设样式,格子
高度 ≈ font-size(14px) 比正文行(line-height ≈19.6px)矮,图标偏离中央。给格子
flex 居中 + line-height:1.4(复刻 CM 的字号×1.4 行高算法),图标落在正文行中央。
2026-07-08 14:03:14 +08:00
533f12e851
fix(editor): 修复 CodeMirror 折叠图标垂直不居中
...
折叠三角 (▾/▸) 在每行行框里偏低,根因有二:
1. 图标字符本身基线不稳。codemirror 包的 basicSetup 调 foldGutter() 用默认
openText "⌄" (U+2304「下花括号」),这是个基线/字形表现不稳定的符号字符,
在多数 mono 字体里墨迹落在字符框底部,三角明显偏低。本地内联一份 basicSetup
(CodeMirror 官方推荐的定制方式),改用几何形状块字符 ▾ (U+25BE) /
▸ (U+25B8),字形本身在字符框内居中;同时把 foldGutter() 展开后丢弃的
@codemirror/{lint,search} 显式声明为依赖。
2. core 没给图标 <span> 设 vertical-align/display。baseTheme 只给
.cm-foldGutter span 设了 padding/cursor,图标默认按基线贴在格子底部。
themes.ts 加一条 flex 居中规则,把 .cm-foldGutter .cm-gutterElement 变成
flex 容器,三角稳稳落在行框中央。
文章页 CodeRunner 与后台 SQL 控制台共用同一份 editor.js,一处修复两处生效。
2026-07-08 13:19:59 +08:00
82315853a2
fix(tiptap): slash 命令创建可运行代码块时模态框被立即关闭
...
斜杠菜单中按 Enter 选择「可运行代码块」时,openRunnableModal 在
同一个 keydown 事件处理中同步调用。模态框的 document keydown 监听
立即注册后,该 Enter 事件继续冒泡到 document 触发模态框的 Enter
处理——insert() → close(),模态框在同一 tick 内创建又销毁,用户
看不到配置弹框。
将 document.addEventListener("keydown", ...) 延迟到 setTimeout(0)
注册,确保触发事件完成传播后才开始监听。
2026-07-07 17:51:06 +08:00
1fa1984703
feat(editor): 点语言标签编辑可运行代码块的语言与配置
...
openRunnableModal 新增编辑模式(editPos + currentInfo):
- 回填当前块的 lang + overrides(解析 info string)
- 标题改「编辑」、按钮改「保存」
- 确认用 setNodeMarkup 原地更新 language 属性(保留块内代码)
CodeBlockNodeView 的语言标签在 runnable 块上可点击(绿字 hover 提示),
点击触发编辑模式(getPos 传 NodeView)。update() 在 language 变化时同步
刷新 <code> 的 class,让高亮按新语言重算。
补 4 个编辑模式用例(回填/标题/保存走 setNodeMarkup/改语言写入)。
Playwright 验证:python→node 编辑后 lang 标签与 code class 同步更新。
2026-07-07 16:12:11 +08:00
9c8ab791d9
fix(editor): codeBlock 内 Backspace 在 lowlight decoration 重建后失效
...
根因:CodeBlockLowlight 的 lowlight 插件在 codeBlock 内容变化后重新高亮,
重建 <code> 内 text node(包成 <span class=hljs-...>)。重建过程导致 DOM
selection 错位到 <pre>(contentDOM 父元素)而非 <code> 内文本,后续 Backspace
删不掉剩余字符,块删空后也无法删除整个 codeBlock。
文档层 ProseMirror selection($anchor)始终正确,只是 DOM 层不同步。用独立
Extension + handleKeyDown plugin prop 接管 codeBlock 内的 Backspace:基于
文档 pos 直接删除字符(不依赖 DOM selection),或在块空时 setNodeMarkup
转段落。handleKeyDown 优先级高于 keymap,能覆盖父类 CodeBlock 的同名 shortcut
(在 CodeBlockLowlight.extend 里 addKeyboardShortcuts 无法覆盖父类,故独立扩展)。
经 Playwright 真实浏览器逐键验证:输入 ab → 删 b → 删 a(此前失败) → 删块,
全链路正常。与 NodeView 无关(禁用 NodeView 同样复现,根因在 lowlight decoration)。
2026-07-07 14:05:53 +08:00
df8f7397b2
fix(editor): 斜杠命令文本残留进新节点(/code 带入 codeBlock)
...
Suggestion 传给 command 的 range 在某些输入路径下过时——停在首次
输入 '/' 的位置 {from:1,to:2},不随后续字符更新。deleteRange 只删 '/',
命令文本(如 'code')残留,被 toggleCodeBlock 包进新节点。
该 bug 同时导致衍生症状:残留文本让 codeBlock 非空,Backspace 的
clearNodes 条件(!textContent.length)不成立,空块退格看似无效。
修复:command 转发处防御性重算 range——基于当前 selection 在同一段落
内往前找触发字符 '/',删到光标位置。对所有命令统一生效。
经 Playwright 真实浏览器复现验证(happy-dom 无法复现此 bug):
- /code 回车:codeBlock 现为空(原残留 'code' 文本)
- 空 codeBlock 退格:正常删除变段落(原无反应)
2026-07-07 11:59:26 +08:00
7fbaae7622
feat(editor): 斜杠命令空状态显示「无匹配命令」
...
搜索无结果时浮层显示居中提示(非空白卡片)。同步修两个空列表 bug:
- ArrowUp/Down 对 % 0 产生 NaN(除零)
- Enter 被吞(preventDefault 后无项可选,用户回车无反应)
空列表时方向键/回车放行(return false),仅 Escape 拦截关闭。
导出 createPopup 补 6 个 happy-dom 用例。
2026-07-07 10:24:54 +08:00
9a90514470
feat(editor): 斜杠命令支持英文别名搜索(中英文互通)
...
每条命令补 keywords 字段(空格分隔英文/常见词),搜索时 title/description/
keywords 任一命中即匹配。/code 现在能命中「代码块」,/link 命中「链接」。
提取 matchCommand 纯函数(原 inline filter),补 30 个用例覆盖中英文命中、
大小写不敏感、keywords 缺省回退、不命中场景。
2026-07-07 10:15:44 +08:00
d8036c6e70
fix(editor): 代码块内 Backspace 删整块(ignoreMutation 误忽略 contentDOM 编辑)
...
CodeBlockNodeView.ignoreMutation 无条件返回 true,导致 contentDOM(<code>)
内的 characterData/childList mutation 被忽略,ProseMirror 文档状态与 DOM
失同步——输入文字它不知道,Backspace 时基于错误状态重建,表现为删整块。
改为对齐 ProseMirror/Tiptap 默认 NodeView 逻辑:
- contentDOM 内的 mutation(characterData/childList)→ false(交给 PM)
- selection → false
- contentDOM 自身 attributes(高亮改 class)→ true(忽略)
- 工具栏/结果区装饰元素 → true(忽略)
补 2 个回归用例覆盖 contentDOM 内/外分支。参数类型改 ViewMutationRecord
(含 selection,ProseMirror 扩展类型)。
2026-07-07 10:03:36 +08:00
c8b2248066
fix(editor): runnable 块 classList.add 抛 InvalidCharacterError
...
CodeBlockNodeView 构造时把完整 info string 原样拼进 class
(`language-python runnable {...}`),classList.add 拒绝含空格 token,
导致编辑模式回填含 runnable 块的文章时崩溃(setMarkdown → addNode)。
改用 extractLang 只挂纯语言名 class。高亮靠 CodeBlockLowlight decoration,
本就不依赖此 class。补回归测试。
2026-07-07 09:41:05 +08:00
763edc300d
feat(editor): NodeView 适配 onRunCode 协议(extractLang + overridesJson 提取)
...
runCode 改传纯语言名(extractLang) + overrides JSON 字符串(extractOverridesJson),
对齐 Task 4 Rust 桥接 RunCodeOptsJs(language/source/overrides_json):
- highlight.ts: 新增 extractOverridesJson,对齐后端 parse_fence_info 取以 { 开头的 token
- code-block-view.ts: RunCodeOpts 加 overridesJson 字段,runCode 改传 extractLang/extractOverridesJson 提取结果
- index.ts: EditorOptions.onRunCode 类型同步加 overridesJson
- 测试: extractOverridesJson 6 例 + code-block-view 点击断言更新(纯语言名 + overridesJson)
2026-07-06 18:17:33 +08:00
6bdc48e84c
feat(editor): CodeBlock NodeView 工具栏与结果区样式
...
语言标签 + 运行按钮 + 结果区,亮/暗双套 Catppuccin 配色。
runnable 绿条移到 .tiptap-codeblock 容器(替换旧 pre 绿条规则)。
2026-07-06 18:02:32 +08:00
6e5ff72a29
feat(editor): 挂载 CodeBlockNodeView + editor.storage 注入 onRunCode
...
CodeBlockLowlight.extend({ addNodeView }) 接入自定义 NodeView。
EditorOptions 新增 onRunCode 字段;编辑器初始化时挂到 editor.storage,
NodeView 经 storage key 读取(仿 upload-coordinator 范式)。
2026-07-06 18:00:15 +08:00
82280e3d4f
feat(editor): CodeBlockNodeView 显示语言标签与运行按钮
...
自定义 NodeView 包裹 <pre><code>,顶部工具栏显示语言(extractLang 提取),
runnable 块额外显示运行按钮。contentDOM 指向 <code>,保证 CodeBlockLowlight
decoration(高亮)正常生效。运行走 editor.storage.__onRunCode 回调。
2026-07-06 17:59:01 +08:00
0adc6d8382
fix(editor): 暗色 type 色值修正 + extractLang 小写化
...
- .dark .hljs-type/.hljs-title.class_ 由 #8839ef(紫) 改为 #f9e2af(黄),
与 spec 表、官方 @catppuccin/highlightjs、读者侧 highlight.css 三处对齐
- extractLang 加 toLowerCase,与后端 parse_fence_info(languages.rs:85)对齐,
避免大写语言名触发 lowlight Unknown language 抛错
2026-07-06 16:13:08 +08:00
4de48cd16c
style(editor): biome 修正 import 排序(highlight/index)
...
TDD 阶段子代理未跑 biome check --write,补齐 import 字母序以过 make lint。
2026-07-06 15:53:16 +08:00
8493e4c4c4
feat(editor): 代码块语法高亮配色(Catppuccin Latte/Mocha)
...
色值取自 @catppuccin/highlightjs@1.0.1,与读者侧 highlight.css 同一 palette。
亮/暗双套,.dark 前缀切换,scope 到 .tiptap-editor 避免污染全站。
2026-07-06 15:50:35 +08:00
e86fe0767d
feat(editor): 用 CodeBlockLowlight 替换 StarterKit 自带 CodeBlock
...
禁用 StarterKit codeBlock,引入 CodeBlockLowlight + lowlight 实例。
代码块现在有实时语法高亮(ProseMirror decoration),language 属性
仍承载完整 info string(markdown 往返不变)。
2026-07-06 15:48:02 +08:00
e79e0d9ee2
feat(editor): 新增 lowlight 实例与 extractLang 语言提取
...
common 集(~35 种语言)注册到 lowlight;extractLang 从完整 info string
(如 'python runnable {...}')提取首 token,使 runnable 块按正确语言高亮。
2026-07-06 15:45:42 +08:00
0b4346ecd4
fix(editor): 语言下拉展开时 Enter 不误触发插入
...
<select> 展开时 Enter 用于确认选项,不应提交整个弹框。
HTMLSelectElement.open 浏览器存在但 TS lib.dom 未声明,用交叉类型断言。
2026-07-06 14:15:25 +08:00
9b4c0ea36e
style(editor): biome 格式化 runnable 模态框代码
...
biome check --write 修正 import 排序 + 格式化 openRunnableModal 链式调用
(TDD 阶段子代理未跑 make fix,此处补齐以过 make lint)。
2026-07-06 14:08:06 +08:00
01ceef2694
feat(editor): 可运行代码块模态框样式 + runnable 块绿色边框
...
模态框复用 Catppuccin Latte/Mocha 配色;编辑器内含 runnable 标记的
代码块加左侧绿色边框(与 Code Runner 运行按钮主色一致)。
2026-07-06 14:04:52 +08:00