Skip to content

2.6.7

Choose a tag to compare

@Dyse-Sofqi Dyse-Sofqi released this 01 Oct 13:19

2.6.7 (2026-10-01) — 新增「首行缩进」通用功能(为正文段落首行缩进,自动排除标题/表格/列表/引用/代码块等非正文块;宽度 1~2 个中文字符可调;实时预览与阅读视图双端生效,且段落边界跟随 Obsidian 的「严格换行」设置);首行缩进的作用域收窄到真正的正文区域(修复 Glimpse 提词器等第三方视图被误缩进);首行缩进在阅读视图的逐行缩进改用空 inline-block 占位元素实现(修复后续行比首行多缩进一个空格宽);根治「设置开关改了要重启 Obsidian 才生效」(传播链异常隔离 + 全窗口运行态 + 跨 realm 重绘 + 符号边界提示弹框即时消失四类缺口)

新增功能

  • 「首行缩进」(新设置项,默认关闭)—— 正文段落首行自动缩进,非正文块一律排除 — 通用设置区新增开关与宽度滑块(1~2 个中文字符,1em = 一个中文字符宽)。判定走自研行分类器(纯函数,不依赖宿主语法树),排除标题、表格、无序/有序列表、引用与 callout、代码块、数学块、注释、HTML 块、frontmatter、脚注与链接引用定义、独立块 ID、纯图片段落。仅实时预览与阅读视图生效,源码模式刻意不生效(缩进会把源码本身推右,看起来像误输入的空格)。缩进由 CM6 行装饰驱动(编辑器侧)与样式表(阅读视图侧)共同实现,共用同一份宽度变量。设置项说明里写明它的适用范围与排除项。

问题修复

  • 首行缩进的段落边界没有跟随 Obsidian 的「严格换行」设置 — 原先按 CommonMark 语义判定「连续文本行 = 同一段落,只有第一行是首行」,但 Obsidian 的默认设置就是非严格换行(单个换行在渲染中即一次硬换行),此时用户按单回车写段落是「一行即一段」,于是除首行外的每个正文行都漏了缩进。现在读取 app.vault.getConfig('strictLineBreaks') 并跟随该设置:非严格(默认)时每个正文行都是段落首行、都缩进;严格时保持原行为(同段续行不缩进)。该设置变化时即时重绘(订阅 config-changed 并筛该键),段落判定缓存键也带上该标志位。
  • 首行缩进影响了 Glimpse 提词器等第三方视图 — 两条作用域都过宽:阅读视图侧按 .markdown-preview-view p 选,而 markdown-preview-view / markdown-rendered 常被第三方视图借去复用主题样式(Glimpse 提词器的内容容器就是 glimpse-tp-content markdown-rendered markdown-preview-view),于是提词器里的段落也被缩进;编辑器侧的行装饰则是 registerEditorExtension 注册的,会挂到工作区里所有 CM6 编辑器上(含第三方视图自建的编辑区)。现在阅读视图规则前缀收一层到 .markdown-reading-view(排除项同步加前缀),编辑器侧在 build() 里要求 view.dom.closest('.markdown-source-view')。两侧都要收 —— 编辑器侧是 JS 装饰,只改样式表拦不住。
  • 阅读视图里首行缩进的后续行比首行多缩进约 0.3~0.4 个字符宽 — 占位元素原先插在 <br> 与紧随的文本节点之间,而 markdown-it 渲染软换行输出的是 '<br>\n'(<br> 后跟一个换行符,会被 HTML 解析器并进紧随文本节点的开头,DOM 形如 "\n乙段内容")。这段前导空白因此不再位于行首、不被「行首空白丢弃」吃掉,而是塌缩成一个空格把该行多推一个空格宽(实测后续行 44.73px vs 首行 40px,差值 4.73px @16px 字号 = 0.3em,正是一个空格)。现在占位元素插到前导空白之后(splitText 切开,前导空白留在 <br> 之后),实测修复后两行左边界均为 40px。
  • 阅读视图缺少逐行缩进(单回车换行的后续行不缩进) — 阅读视图里单个回车只是 <p> 内的一个 <br>,而 text-indent 只作用于块级容器的首行,<br> 之后的文本拿不到缩进(四种纯 CSS 做法实测全部无效,见下)。现在在正文段落里每个 <br> 之后插入一个空的 inline-block 占位元素把该行撑右:空节点不进文本内容(选中复制不受影响)、height: 0 使段落高度与插入前逐像素一致(实测 76.78 = 76.78)、宽度走 CSS 变量(改宽度无需重插)、撤销只需删掉带类名的节点。插入时机靠 MutationObserver(childList+subtree,rAF 合并),apply() 幂等(只在需要补/摘时动 DOM,避免自激),容器用 WeakSet 去重。仅在「功能开启 且 非严格换行」时插入(严格换行下 <br> 只来自显式硬换行、仍属同一段落,不该缩进)。
  • 「设置开关改了要重启 Obsidian 才生效」 — 先证明链路本身是通的(静态审计 64 个设置键的消费点 + 机械审计 38 个 Setting 块均无遗漏 + CM6 源码核实空事务确实会走到 ViewPlugin.update() + 真实 CM6 端到端实测开关开/关/再关得到 0/3/0 个行装饰),再修掉四类真实缺口:① 传播链可被单点异常静默掐断 —— saveSettings() 里 syncConfig() → repaintAllEditors() → dirFileCountRefresher.forceRefresh() 顺序直连,任一步抛错后面全不执行且无日志,症状与「设置已保存但插件没反应、重启才生效」完全一致;现在逐段 try/catch + console.error。② body 运行态只挂 activeDocument —— popout / 悬浮编辑器窗口里的编辑器拿不到 body 开关类与 CSS 变量,新增 window-scope.ts 的 forEachDocument() 遍历主窗口 + 各 leaf 的 document(按 document 去重,用 node.ownerDocument ?? node 判定,因为 popout 的 document 来自另一个 realm、instanceof Document 会失败),四个 body 类模块全部改为全窗口应用。③ 重绘用 instanceof MarkdownView —— popout 的视图跨 realm,instanceof 为假、那些编辑器不会被重绘;改为鸭子判定并给每个编辑器套 try/catch。④ 「符号边界提示」关掉后弹框不消失 —— 它的 update() 只在选区/文档/几何变化时刷新,而改设置只派发空事务,已显示的弹框要等下次光标移动才消失;补一个「开关翻转」判定并清掉位置缓存。另新增 applyRuntimeClasses() 自愈点(幂等 + 逐项异常隔离),由 saveSettings()、workspace.on('layout-change')(新开 popout 时补挂)、设置面板 display()(打开即自愈)三处调用。

界面优化

  • 首行缩进的性能与交互 — 段落判定按文档对象缓存(行分类是 O(行数) 的全篇扫描,只在文档真的变化时重算,纯光标移动/滚动复用上次结果);装饰只给可见范围内的段落首行建。text-indent 不破坏 CM6 的光标定位(posAtCoords 走布局感知接口,因此点击落点与选区矩形自动跟随)。

完整更新记录:CHANGELOG.md / CHANGELOG.en.md