鸿蒙 PC Markdown 编辑器即时渲染语法矩阵:结构降级、离线图片与光标可编辑性
鸿蒙 PC Markdown 编辑器即时渲染语法矩阵结构降级、离线图片与光标可编辑性即时渲染最容易被误解为“把 Markdown 变成富文本”。如果实现只追求视觉效果确实可以先把正文渲染成 HTML再让用户编辑 DOM最后反向生成 Markdown。但是这条路线会把空格、换行、标记风格、引用缩进和链接写法重新排列。对于把 Markdown 文件交给 Git、静态站点、团队仓库或其他编辑器继续处理的用户这种重排不是小瑕疵而是文本事实来源发生了变化。本文讨论另一条路线在鸿蒙 PC 编辑器中始终保留同一个 CodeMirrorEditorState借助 Lezer Markdown 语法树和 Decoration 改变屏幕呈现。用户看到的是标题、链接、引用、列表、代码和图片预览磁盘中保存的仍然是原始 Markdown。光标进入结构后必要标记立即恢复语法未闭合、结构有歧义或超出支持范围时局部直接显示源码。对应工程仓库为 https://gitcode.com/VON-/codex_md_oh本文基于提交1bf4b62。代码、测试数字和截图均来自该提交的实际实现。本文是一篇独立技术文章不要求读者先了解项目的其他阶段。即时渲染的正确验收对象是文本不变量“标题看起来更大”只能证明 CSS 生效不能证明编辑器可靠。即时渲染真正需要守住的不变量至少包括五项。第一源码、即时、分栏和预览必须共享同一个文本缓冲区。模式变化不创建另一份可写正文也不能从预览 DOM 回填 Markdown。第二Decoration 只能改变显示不能作为编辑事务写入正文。切换模式前后调用getDocument()返回内容必须完全一致。第三光标必须能进入结构。如果链接目的地、强调标记或图片语法被永久隐藏用户将无法修改它们。隐藏必须以当前选择区是否接触结构为条件。第四未闭合语法必须回退源码。解析器没有确认完整边界时编辑器不能凭猜测隐藏字符。例如[链接](target.md缺少右括号三个反引号代码围栏缺少结束标记都应原样显示。第五大文档保护策略优先于视觉能力。精确 10 MiB 文档进入保护模式后即时 Decoration 和隐藏的专业预览都必须关闭保证保存和源码阅读仍可执行。这五项约束决定了本次实现不会建立“富文本模型 Markdown 模型”的双向同步也不会在一个功能步骤里覆盖所有 Markdown 方言。语法矩阵按结构逐项扩展每一项都包含正常、嵌套、光标进入和错误降级边界。用语法树定义可以安全隐藏的边界实现入口位于web-editor/src/instant-rendering.ts。插件读取 CodeMirror 当前状态中的 Lezer 语法树只遍历可见范围避免长文档滚动时为不可见节点创建无意义 DOM。functioncreateInstantDecorations(view:EditorView,options:InstantRenderingOptions):DecorationSet{if(!view.state.field(instantRenderingEnabled)){returnDecoration.none;}constranges:ArrayRangeDecoration[];consttreesyntaxTree(view.state);for(constvisibleRangeofview.visibleRanges){tree.iterate({from:visibleRange.from,to:visibleRange.to,enter:(node){// 每种结构只在解析器确认的节点边界内生成 Decoration。}});}returnDecoration.set(ranges,true);}这里没有把 Markdown 交给正则表达式逐行替换。正则很难正确处理嵌套强调、引用中的列表、链接标题、转义字符和未闭合结构。语法树节点已经给出了结构名称、起止偏移和父子关系Decoration 只需要在这些边界内工作。选择区判断同样保持简单。只要任意选择范围与节点相交就视为用户正在编辑该结构不隐藏必要标记。functionselectionTouches(view:EditorView,from:number,to:number):boolean{returnview.state.selection.ranges.some((range)range.fromtorange.tofrom);}这个规则故意偏保守。即使光标落在结构边界也宁可多显示一次源码标记不能让用户无法定位。即时渲染的价值是减少视觉噪声不是剥夺源码编辑能力。ATX 与 Setext 标题需要不同的显示策略ATX 标题使用行首#Setext 标题使用下一行的或---。两者在语法树中分别表现为ATXHeading1至ATXHeading6、SetextHeading1和SetextHeading2。统一的层级解析如下。functionheadingLevel(nodeName:string):number|undefined{constmatch/^(?:ATX|Setext)Heading([1-6])$/.exec(nodeName);returnmatch?Number.parseInt(match[1],10):undefined;}标题正文通过行 Decoration 获得字号、字重和分隔线HeaderMark只有在选择区没有接触标题时才隐藏。Setext 的标记单独占一行如果简单设置成零高度编辑器 gutter 中的行号会重叠。设备视觉验收第一次就发现了这个问题6 像素的折叠高度无法容纳行号。最终版本保留 18 像素稳定高度既降低标记行存在感也不破坏行号定位。.cm-line.cm-instant-setext-marker, .cm-line.cm-instant-code-fence{min-height:18px;height:18px;overflow:hidden;line-height:18px;}这说明桌面编辑器不能只看正文 DOM。源码行号、折叠标记、滚动定位和选择映射都属于同一交互系统。过度压缩某一行视觉上可能“更像排版软件”却会制造定位重叠和点击误差。链接只隐藏已经确认完整的目标普通行内链接[文本](target.md)、带标题链接和显式引用链接可以安全收起目标部分但未闭合链接不能处理。Lezer 对[链接](target.md可能只识别出前面的[链接]节点如果看到两个LinkMark就直接隐藏左方括号会消失而后面的不完整目标仍留在屏幕上。最终实现增加了完整目标判断行内链接必须拥有完整的括号标记引用链接必须拥有LinkLabel。只有条件成立时才隐藏开头方括号以及从文本闭括号到节点末尾的目标部分。constlinkMarks[];lethasReferenceLabelfalse;letchildnode.node.firstChild;while(child){if(child.nameLinkMark){linkMarks.push(child);}elseif(child.nameLinkLabel){hasReferenceLabeltrue;}childchild.nextSibling;}consthasCompleteTargetlinkMarks.length4||hasReferenceLabel;if(linkMarks.length2hasCompleteTarget!selectionTouches(view,node.from,node.to)){ranges.push(Decoration.replace({inclusive:false}).range(linkMarks[0].from,linkMarks[0].to));ranges.push(Decoration.mark({class:cm-instant-link}).range(linkMarks[0].to,linkMarks[1].from));ranges.push(Decoration.replace({inclusive:false}).range(linkMarks[1].from,node.to));}自动链接https://example.com隐藏两侧尖括号并保留 URL普通裸 URL 只增加链接颜色和下划线不改写内容。链接在即时模式中只是显示为链接不直接发起网络访问。预览中的本地跳转仍经既有原生命令和授权边界处理外部链接保持受限。下图来自 MateBook Pro 2in1 鸿蒙模拟器。光标位于空白行时Setext 标记、链接目标、引用标记、代码围栏和图片语法按规则收起行号没有重叠。当光标进入链接结构时完整[本地文档](docs/guide.md)立即恢复其他结构仍保持即时显示。这不是单独维护的“编辑视图”只是同一语法树在选择变化后重新计算 Decoration。图片预览复用受限 Bridge 而不是开放网络图片是即时渲染中风险最高的结构之一。直接把 Markdown 的src写入img会带来两个问题本地用户路径不能被 ArkWeb 任意读取远程 URL 又可能在用户不知情时发出网络请求。本次实现只复用已有的受限图片链路。Markdown 图片节点被替换为固定尺寸 Widget如果当前文档会话已经缓存了对应 Blob URL就显示真实图片。没有缓存时只向requestImageSource提交原始 Markdown 路径。主模块继续执行两段式相对路径校验、授权目录读取、图片 MIME 白名单、8 MiB 上限、64 KiB 分块传输和会话隔离。exportinterfaceInstantRenderingOptions{resolveImageSource?:(markdownPath:string)string|undefined;requestImageSource?:(markdownPath:string)void;}constpreviewUrloptions.resolveImageSource?.(source);if(!previewUrl){options.requestImageSource?.(source);}ranges.push(Decoration.replace({inclusive:false,widget:newInstantImageWidget(source,altText,previewUrl,node.from,node.to)}).range(node.from,node.to));外部 URL 无法通过相对路径校验因此不会进入 Bridge也不会加载网络图片。Widget 显示固定的替代文本占位截图中的“外部图片”就是该安全降级。对于有效的本地工作区图片Bridge 返回分块数据后创建 Blob URL再通过显式状态 effect 刷新即时 Decoration。functionrefreshInstantImages(sessionId:string):void{if(sessionIdactiveSessionIdcurrentModeinstant){editor.dispatch({effects:refreshInstantRendering.of(null)});}}Widget 使用固定的 520 x 220 像素上限图片以object-fit: contain显示。这样加载完成前后不会突然把编辑器内容推开。用户点击图片 Widget 时选择区移动到图片源码内部下一次 Decoration 计算会撤掉 Widget并恢复![alt](path)从而继续编辑替代文本和路径。引用与列表优先保留结构语义引用节点的视觉目标不是生成第二份 HTML blockquote而是在编辑器行上增加左边界和文字颜色。每一个QuoteMark在结构未被选择时隐藏Blockquote覆盖的行获得同一类名。嵌套强调、链接和行内代码仍由它们自己的节点处理。列表则采用更保守的策略。ListItem提供轻微纵向间距ListMark保留在文本中并使用主题色和加粗。无序列表没有把-替换为私有项目符号有序列表也不伪造自动编号。用户仍能清楚看到源文件使用了哪一种标记同时获得比源码模式更稳定的层级视觉。这种保守处理还有一个现实理由列表延续、Tab 缩进和任务列表编辑属于后续结构化编辑事务。当前阶段只改变显示不应提前改变 Enter、Tab 或撤销语义。视觉 Decoration 和结构编辑命令分开验收可以明显缩小数据损坏的风险面。行内代码与围栏代码块采用两级降级完整的行内代码节点拥有两个CodeMark。结构未被选择时隐藏反引号内容使用等宽字体、浅色背景和细边框光标进入时反引号恢复。未闭合反引号没有完整节点因此保持源码。围栏代码块首先确认至少存在开始和结束两个CodeMark。如果只有开始围栏整个节点不生成即时样式。完整结构的所有行获得统一等宽背景开始行的语言信息与结束围栏在光标离开时隐藏标记行保留 18 像素稳定高度。光标进入代码块后围栏和语言标识全部恢复。if(node.nameFencedCode){constcodeMarks[];letchildnode.node.firstChild;while(child){if(child.nameCodeMark){codeMarks.push(child);}childchild.nextSibling;}if(codeMarks.length2){returnfalse;}addLineRangeDecorations(ranges,view,node.from,node.to,cm-instant-code-block);}即时编辑器没有在代码块内部运行 Highlight.js。专业高亮仍属于预览管线带有异步取消和二次净化。即时模式选择低成本等宽样式避免每次输入都启动另一套代码高亮 DOM也避免隐藏编辑器内核本身的 Markdown 语法状态。为什么需要显式刷新图片而不刷新全部预览CodeMirror 插件在模式变化、正文变化、选择变化和视口变化时重算可见 Decoration。本地图片读取是异步事件完成时正文没有变化所以增加了专用refreshInstantRenderingeffect。这个 effect 只让 Decoration 重算不修改文档也不进入撤销历史。constrefreshRequestedupdate.transactions.some((transaction)transaction.effects.some((effect)effect.is(refreshInstantRendering)));if(modeChanged||refreshRequested||update.docChanged||update.selectionSet||update.viewportChanged){this.decorationscreateInstantDecorations(update.view,options);}即时模式仍然不会在每次输入时生成隐藏的 markdown-it、KaTeX、Mermaid 和 Highlight.js 完整预览。只有分栏和阅读模式需要专业预览。这个边界对鸿蒙 PC 的输入延迟和 ArkWeb 内存尤为重要用户选择即时写作不应在后台承担双栏渲染的全部成本。自动化语料覆盖正常、交互与失败路径本轮新增三项 Playwright 用例使 Web 全量从 47 项增加到 50 项。第一项使用 Setext 标题、行内链接、自动链接、引用、无序列表、有序列表、行内代码和 TypeScript 围栏代码块验证对应类名、隐藏结果和getDocument()完全相等。第二项使用一个本地图片和一个远程图片。测试 Bridge 只收到本地Note.assets/instant.png本地图片最终使用blob:URL远程图片只显示替代文本。点击本地 Widget 后图片源码恢复而文档内容不变。第三项输入未闭合链接与未闭合代码围栏验证两者都显示源码且不存在代码块即时类名。这项用例直接防止“解析一半也隐藏一半”的错误。定向测试最终为 6/6全量 Playwright 为 50/50。精确 10 MiB Chromium 保护模式回归本轮记录 321 ms远低于三秒门槛但这个数字只代表当前本机 Web 环境不能替代鸿蒙 PC Release 真机的读取、Bridge、输入 P95 和内存结果。鸿蒙模拟器验证暴露了自动化看不到的行号问题最终 Debug HAP 安装到 MateBook Pro 2in1 模拟器。通过设备 UI 自动化输入精确多行 Markdown切换“源码/即时”把光标分别放在空白行和链接结构中再截取 3120 x 2080 应用画面。第一次视觉检查发现隐藏 Setext 标记行和代码围栏行只有 6 像素正文虽然没有重叠gutter 中相邻行号却挤在一起。实现随后把稳定高度调整为 18 像素重新执行全量构建、安装和截图。最终截图中第 14、15、16 行保持清晰代码背景、图片占位和状态栏也没有相互遮挡。最终./scripts/verify-local.sh通过包含 Playwright 50/50、生产单 HTML、Debug HAP、ArkTSUnitTestBuild和差异检查。最终 ohosTest HAP 重新构建、安装并执行结果为 11/11Failure 0、Error 0总耗时 2520 ms。产物事实如下Debug HAP8,558,158 字节SHA-256ba78366e06b46e490f96174c883f3a07fcccdb052e4a7526b278b50a176a7473。ohosTest HAP9,335,601 字节SHA-256d2cc792a4da230274a7f5d09c4b7ecce8da6543a7775a72847bc6bdd4b10689f。语法矩阵截图SHA-25635ba49beb53b3a0b458109205361b7e613cff3786604ff710b7bf7a52a138dd3。链接显标截图SHA-25649cc97f2c33d0311bf1da7b8d949d2c4d150cddd9f835c03b944b428560d6eae。当前语法矩阵的明确边界当前即时模式已经覆盖 ATX H1-H6、Setext H1-H2、粗体、斜体、完整行内链接、显式引用链接、自动链接、裸 URL、图片、引用、无序/有序列表、行内代码和完整围栏代码块。它没有把表格、任务复选框、删除线、Front Matter、HTML 块、公式和 Mermaid 伪装成已完成能力。这些结构继续显示源码专业排版可在分栏或阅读模式查看。即使已覆盖的类型遇到未闭合或无法确认边界也会局部回退源码。本地图片可以显示真实 Blob 预览外部图片默认不会联网只显示稳定占位。图片点击显标已经由自动化覆盖鸿蒙模拟器截图展示的是外部图片安全降级。真实工作区图片、物理键盘、触控板、输入法组合和 Release 性能仍需要鸿蒙 PC 真机矩阵复核。工程结论即时渲染的竞争力不在于“隐藏了多少 Markdown 符号”而在于隐藏之后仍能可靠编辑、撤销、保存和跨软件交换原文件。基于同一EditorState的 Decoration 路线把显示层与文本事实来源分开使每种语法都可以独立增加、独立降级和独立测试。对鸿蒙 PC 编辑器而言这种路线还保留了平台侧优势ArkUI 继续负责窗口、文件授权和系统能力ArkWeb 只处理本地编辑器图片仍通过最小权限 Bridge 读取远程内容不会因为即时显示而突破离线策略大文档仍能回到源码模式。下一阶段将从“结构显示”进入“结构编辑辅助”重点是列表延续、缩进、自动配对和表格行列命令。届时验收对象不再只是 Decoration而是每个操作能否形成一次可整体撤销的 CodeMirror 事务并保持空格、换行和 Markdown 标记不被无意重排。

相关新闻

C++单位安全编程:用编译期维度分析杜绝数值计算错误

C++单位安全编程:用编译期维度分析杜绝数值计算错误

1. 项目概述:为什么我们需要一个“带单位的变量”?在嵌入式开发、物理仿真、游戏引擎或者任何涉及数值计算的C/C项目中,我们每天都在和数字打交道。比如,你写下一行代码float distance 100.0;,然后调用一个函数calcul…

2026/10/13 11:01:34 阅读更多 →
为什么有时候选择手搓Agent?

为什么有时候选择手搓Agent?

这是 Agent 工程里面一个很常见的架构选择问题。很多公司会问:为什么不用 LangChain、AutoGen、CrewAI 这些成熟框架,而要自己实现 Agent?答案不是“自己写一定更好”,而是业务需求、可控性、性能、复杂度决定是否手搓。一 、什么…

2026/10/12 23:05:43 阅读更多 →
C++并发安全HashMap设计:锁分段、读写锁与无锁编程实战

C++并发安全HashMap设计:锁分段、读写锁与无锁编程实战

1. 项目概述与核心价值最近在社区里看到不少关于C并发编程的讨论,尤其是涉及到高性能容器时,大家普遍对标准库的std::unordered_map在并发环境下的表现感到头疼。自己动手实现一个线程安全的哈希表,似乎成了每个想深入理解C并发与数据结构的开…

2026/10/2 2:59:53 阅读更多 →

最新新闻

机器学习入门第一性原理:线性模型核心推导与工程实践

机器学习入门第一性原理:线性模型核心推导与工程实践

1. 为什么线性模型是机器学习入门的“第一性原理”很多人学机器学习,一上来就被各种深度网络、注意力机制、Transformer 架构唬住了,觉得线性模型太简单、太基础,不值得花时间。但我带了这么多届学生、看了这么多份简历和项目,一个…

2026/10/13 11:02:04 阅读更多 →
HarmonyOS实战:用PixelMap实现图片颜色翻转与负片效果

HarmonyOS实战:用PixelMap实现图片颜色翻转与负片效果

如果你在搜索HarmonyOS开发里关于 PixelMap 的资料,看到最多的解释多半是“PixelMap是一张位图对象”。这句话没错,但基本等于没说。真正上手做图像处理时你会发现, PixelMap 既不是 Image 组件里那个能直接显示的图片,也不…

2026/10/13 11:02:04 阅读更多 →
免费绘画软件FireAlpaca完全指南:轻量替代PS/SAI的插画与动画实践

免费绘画软件FireAlpaca完全指南:轻量替代PS/SAI的插画与动画实践

1. 我为什么从 PS 和 SAI 换到 FireAlpaca 先交代背景。我一直是个靠画图吃饭的人,平时主要用 Photoshop 做商业插画和排版,但说实话,PS 对我的工作负担太重了:启动慢、吃内存、订阅费一年比一年贵。前几年还流行用 SAI&#xff0…

2026/10/13 11:02:04 阅读更多 →
级联故障风险评估:随机化学算法如何高效搜索最坏停电场景

级联故障风险评估:随机化学算法如何高效搜索最坏停电场景

做电力系统风险评估的同行,应该都遇到过这个尴尬:蒙特卡洛抽样跑了几万次,期望损失算出来漂漂亮亮,可一旦想回答“哪几组初始故障组合能把系统搞瘫”,统计结果往往是“查无此案”。原因很简单,大面积停电属…

2026/10/13 11:02:04 阅读更多 →
全链路 Trace 标签化染色与慢执行步骤智能归因

全链路 Trace 标签化染色与慢执行步骤智能归因

在传统的微服务架构中,一次 HTTP 请求通常沿着“网关 -> 业务中台 -> 缓存/数据库”的确定性静态调用拓扑展开,分布式追踪系统(如 Jaeger、Zipkin、SkyWalking)能够非常清晰地展现各服务的 RPC 耗时与依赖拓扑。 然而&#…

2026/10/13 11:02:04 阅读更多 →
C++数组指针字符串:从内存视角彻底搞懂核心难点

C++数组指针字符串:从内存视角彻底搞懂核心难点

我知道很多人第一次翻开郑莉这本《C语言程序设计》,到第六章"数组、指针和字符串"就开始心里打鼓。说实话,我当年也是这样:前面的流程控制、函数调用,照着抄总能写出来,可数组和指针一上来,什么&…

2026/10/13 11:01:04 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →