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
d6fc98d253
feat(editor): 斜杠菜单新增「可运行代码块」条目
...
触发 openRunnableModal,配置后插入标准 CodeBlock(language 承载
'python runnable {...}' info string)。
2026-07-06 14:02:12 +08:00
5929ab68b4
feat(editor): 新增 openRunnableModal 模态框配置可运行代码块
...
原生 DOM 模态框(遮罩+卡片),语言 select + 超时/内存/网络 3 项 overrides
+ 实时预览。确认插入 setCodeBlock({language: 'python runnable {...}'}),
Esc/遮罩/取消关闭不插入。
2026-07-06 13:58:51 +08:00
84b3da9db6
feat(editor): 新增 buildRunnableInfo 构造 runnable fence info string
...
纯函数,把语言+overrides 配置转成 'python runnable {...}' 形态。
dirty=false 省略 JSON,dirty=true 写 timeout/memory/network 三项,
字段顺序固定。
2026-07-06 13:45:11 +08:00
89bab26b90
fix(lint): themes.ts import 排序(type import 前置)
...
Biome organizeImports 要求 import type 排在普通 import 之前,
交换 @codemirror/view 与 @codemirror/state 顺序。make lint 全绿。
2026-07-06 11:45:14 +08:00
d3c2e41fd8
fix(codemirror): 编辑器塌缩导致上下背景割裂(height:100% 失效)
...
上一提交(3c10f72)用 `& { height: 100% }` 让 .cm-editor 撑满容器,
但实测无效——headless chromium 抓取 computed style 显示:
容器#ed: h=280px (min-height)
.cm-editor: h=63px ← 塌缩!只有内容高度
.cm-gutters: h=63px
.cm-content: h=63px
根因:CSS 百分比高度要求父元素有「明确 height」(不是 min-height)。
容器只有 min-height: 280px,没有 height,`.cm-editor { height: 100% }`
解析为 auto,塌缩到内容高度(3 行 ≈ 63px)。剩余 217px 透出容器背景
(paper-entry/mantle),与 .cm-editor 的 base 色形成上下割裂。
修复:改用 flexbox 而非百分比高度——
- themes.ts:`& { flex: 1 1 0; minHeight: 0 }`(替换 height:100%)
- 容器(system.rs / runner.rs):`display: flex; flex-direction: column`
flex 子项的 `flex: 1` 不依赖父元素 height,能真正填满 min-height 撑开
的空间。headless 验证:.cm-editor 现为 280px,与容器一致,割裂消失。
诊断方法:构建自包含测试 HTML(内联 editor.js),chromium --headless
--dump-dom 抓 getComputedStyle。这是应有的反馈循环,避免了前几轮
凭代码猜测导致的反复误诊。
2026-07-06 00:20:55 +08:00
3c10f72444
fix(codemirror): 编辑器未撑满容器导致上下背景割裂
...
现象:编辑器「有行号的上半部分」与「空白下半部分」背景色不一致。
根因:CodeMirror core 的 base theme 给 `.cm-editor`(&)只设了
position: relative + display: flex,没有 height。编辑器默认只占
内容高度(几行 ≈ 60px),不填满父容器。容器 div 设了 min-height:
280px,但 .cm-editor 不会自动撑到 280px——剩余空间透出容器的
paper-entry(mantle #e6e9ef) 背景,与 .cm-editor 的 base(#eff1f5)
形成「上半 base / 下半 mantle」的上下色差。
修复:themes.ts 的覆盖里加 `& { height: 100% }`,让 .cm-editor
撑满父容器。配合容器已有的 min-height: 280px,编辑器完整填充,
背景统一为 base 色,上下割裂消失。
2026-07-06 00:06:00 +08:00
64907401a0
fix(codemirror): 行号区背景与代码区割裂(core base theme 覆盖 catppuccin)
...
现象:行号列(gutter)背景是 #f5f5f5 浅灰,代码区(content)是 catppuccin
base #eff1f5 浅蓝灰,两者不一致,有明显割裂。
误诊修正:上一提交(e1b312e)以为是容器背景与 CodeMirror 层次扁平,
改了容器为 code-block(crust)。但 CodeMirror 内部 .cm-editor 和
.cm-gutters 都有不透明背景,容器色根本透不出来——所以「没效果」。
本次回退该容器改动。
真因:CodeMirror core 的内置 base theme(@codemirror/view dist index.js:6916)
给 `&light .cm-gutters` 设了 backgroundColor: "#f5f5f5" / dark "#333338 ",
特异性为 .cm-editor.cm-light .cm-gutters,高于 @catppuccin/codemirror
的 .cm-gutters 覆盖,catppuccin 的 base 背景被 core 默认灰压制。
修复:themes.ts 在 catppuccin extension 之后追加一个 EditorView.theme,
用 backgroundColor: transparent !important 强制 .cm-gutters 透明,
继承 .cm-editor 的 base 色,行号区与代码区完全融合。light/dark 同理。
2026-07-05 23:58:18 +08:00