news 2026/9/2 13:57:25

基于Rust+SwiftUI重构macOS原生词典:高性能与现代化体验实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Rust+SwiftUI重构macOS原生词典:高性能与现代化体验实践

作为一名 macOS 开发者,你是否也经历过这样的时刻:想快速查个单词,却不得不忍受系统自带词典那缓慢的启动速度、简陋的界面和有限的词库?或者,你厌倦了在浏览器、第三方 App 和系统工具之间来回切换,只为找到一个准确、美观且能无缝融入工作流的查词方案?

今天要介绍的这个开源项目,正是为了解决这些痛点而生。它不是一个简单的“替代品”,而是一次对 macOS 原生词典体验的彻底重构。核心判断是:一个优秀的词典工具,其价值不仅在于“查得到”,更在于“查得快”、“看得爽”和“用得好”。这个项目通过 Rust 的高性能后端和 SwiftUI 的现代化前端,将查词这一高频但常被忽视的操作,提升到了系统级应用的体验水准。

本文将带你深入剖析这个名为“macOS 原生词典”的开源项目。我们不止步于介绍它是什么,更要拆解它为什么重要——它解决了传统词典工具的哪些顽疾,其技术栈(Rust + SwiftUI)的选择背后有何深意,以及它如何通过开源模式构建更优质的词典生态。更重要的是,我会提供一份从零开始的完整实践指南,包括环境搭建、源码编译、核心功能体验以及深度定制的方法,让你不仅能“用上”,更能“看懂”甚至“改进”它。无论你是追求效率的普通用户,还是对 Rust/SwiftUI 跨语言开发感兴趣的技术爱好者,这篇文章都将为你提供切实的收获。

1. 为什么我们需要“重写”一个原生词典?

在深入代码之前,我们必须先回答一个根本问题:macOS 自带的词典工具已经存在多年,第三方选择也不少,为什么还要“再造轮子”?这背后是三个长期被忽视但至关重要的用户体验断层。

1.1 性能与响应速度的断层系统原生词典(Dictionary.app)的启动速度在较新的系统上或许尚可,但其内部查询引擎和界面渲染的迟滞感,在频繁查词时会被放大。尤其是在查询专业术语或长句时,卡顿感明显。许多第三方词典应用虽然功能丰富,但往往基于 Electron 或其它跨平台框架,资源占用高,启动和查询同样不够“跟手”。一个理想的词典应该像系统 Spotlight 一样,即开即用,结果立现。

1.2 界面美观与现代交互的断层macOS 的设计语言早已进化到 Big Sur、Ventura 乃至 Sequoia 的现代风格,但系统词典的界面仍停留在多年前的拟物化或早期扁平化设计,与系统整体美学格格不入。字体渲染、间距、动画效果都显得过时。现代用户期待的深色模式完美适配、流畅的交互动画、符合人机交互指南的布局,在旧工具中难以寻觅。

1.3 功能扩展与生态开放的断层系统词典的词库扩展虽然可行,但过程繁琐,且格式受限。更重要的是,其功能是封闭的,开发者无法为其添加新的交互特性(如划词翻译增强、生词本同步、自定义查询快捷键等)。开源项目的核心优势就在于其可塑性。通过开源,开发者社区可以共同维护词库、开发插件、适配新的系统特性(如台前调度、连续互通),让一个工具持续进化,而非僵化不变。

这个开源项目正是瞄准了这三个断层,旨在打造一个“性能原生级、界面现代化、生态开放性”的词典解决方案。它选择 Rust 负责核心的词典数据索引和查询逻辑,确保后端极致的速度和内存安全;用 SwiftUI 构建前端,保证界面100%符合最新的 macOS 设计规范且流畅自然。两者的结合,在技术栈上就奠定了其超越现有工具的基础。

2. 核心架构与技术栈解析:Rust + SwiftUI 的跨界组合

理解这个项目的精髓,需要先理解其技术选型。Rust 和 SwiftUI 分别代表了系统级编程和现代声明式 UI 的两个前沿,它们的组合并非常见,但在此处却显得尤为合理。

2.1 Rust:高性能与安全的数据引擎词典应用的核心负担是海量文本数据的快速检索。Rust 在这方面具有天然优势:

  • 零成本抽象与极致性能:Rust 能像 C/C++ 一样提供对内存和硬件的底层控制,确保查询算法(如倒排索引)可以最高效地执行,几乎没有运行时开销。这对于瞬间返回查询结果至关重要。
  • 内存安全与并发安全:词典数据在初始化后主要是读取操作,但可能涉及多线程预加载或缓存更新。Rust 的所有权系统和类型系统从根本上杜绝了数据竞争和内存错误,保证了应用的长期稳定运行。
  • 出色的包管理与跨平台潜力:Cargo 使得管理词典解析、数据压缩等依赖项非常简单。虽然目前专注于 macOS,但 Rust 核心逻辑的跨平台特性为未来可能的跨平台支持埋下了伏笔。

在项目中,Rust 层很可能扮演了“数据引擎”的角色,负责:

  • 加载和解析.dictionary等格式的词库文件。
  • 构建内存中的高效索引数据结构。
  • 接收查询字符串,执行模糊匹配、精确匹配等搜索算法。
  • 将结构化的查询结果(词条、音标、释义、例句)通过 FFI(外部函数接口)传递给 SwiftUI 前端。

2.2 SwiftUI:声明式的原生用户体验如果说 Rust 是强大的心脏,SwiftUI 就是优雅的外表和灵敏的神经。

  • 真正的原生体验:SwiftUI 是 Apple 官方的现代 UI 框架,用它构建的应用在视觉效果、交互动画(如平滑的展开/收起)、无障碍支持等方面与系统深度融合,这是任何跨平台框架都无法比拟的。
  • 声明式语法与高效开发:SwiftUI 的声明式范式让 UI 构建更直观,更容易维护。开发者可以快速实现复杂的界面布局,如自适应宽度、深色/浅色模式切换、搜索栏焦点管理等。
  • 与系统服务的无缝集成:可以方便地调用系统 API 实现诸如共享、快捷键全局绑定、菜单栏扩展、沙盒内文件访问等功能,让词典真正成为系统工作流的一部分。

前端通过 SwiftUI 构建主窗口、搜索框、结果展示视图,并通过某种桥接机制(如 C 接口、@_cdecl或第三方桥接库)调用 Rust 后端提供的查询函数。这种前后端分离的架构,既保证了核心业务的性能,又享受了 UI 开发的高效与现代化。

2.3 项目架构猜想基于常见模式,我们可以推测其架构大致如下:

[SwiftUI App Layer] |- AppDelegate / App Struct (生命周期管理) |- MainWindowView (主界面) |- SearchBarView (搜索输入) |- DefinitionView (释义展示,支持富文本) |- SettingsView (设置) | |---> [FFI Bridge] (如 `unsafe` C函数调用或 `cbindgen` 生成的头文件) | v [Rust Core Layer] |- lib.rs (库入口,暴露查询接口) |- engine/ (查询引擎模块) |- index.rs (倒排索引构建与查询) |- dictionary.rs (词库格式解析) |- data/ (数据模型) |- entry.rs (词条定义) |- ffi/ (FFI 接口定义)

这种架构清晰地将高性能计算和 UI 渲染分离,是开发高质量原生应用的优秀实践。

3. 环境准备:从零搭建开发与编译环境

要体验或贡献这个项目,你需要配置一个包含 Rust 和 Swift 开发环境的 macOS 系统。以下是详细步骤。

3.1 系统与工具要求

  • 操作系统:macOS 12 (Monterey) 或更高版本。建议使用最新稳定版以获得最佳的 SwiftUI 特性支持。
  • 命令行工具:必须安装 Xcode Command Line Tools。
    xcode-select --install
  • Xcode(可选但推荐):虽然纯命令行也可编译,但拥有 Xcode 可以更方便地管理签名、调试 SwiftUI 预览。可从 Mac App Store 免费安装。
  • Homebrew:macOS 包管理器,用于安装 Rust。如果未安装,请先访问 brew.sh 安装。

3.2 安装 Rust 工具链我们将通过rustup管理 Rust 版本,这是官方推荐的方式。

# 1. 安装 rustup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 2. 安装过程中,选择默认选项(1)即可。 # 3. 安装完成后,配置当前 shell 的环境变量,或新开一个终端窗口。 source $HOME/.cargo/env # 4. 验证安装 rustc --version cargo --version

为了加速国内开发者的依赖下载,可以配置 Cargo 国内镜像。在~/.cargo/config文件中添加(如文件不存在则创建):

[source.crates-io] replace-with = 'ustc' [source.ustc] registry = "git://mirrors.ustc.edu.cn/crates.io-index"

3.3 获取项目源码假设项目托管在 GitHub 上(这是最可能的情况),我们使用git克隆代码。

# 进入你的开发目录 cd ~/Developer # 克隆项目仓库(此处以占位仓库为例,实际地址需替换) git clone https://github.com/username/native-macos-dictionary.git cd native-macos-dictionary # 查看项目结构 ls -la

一个典型的项目结构可能包含:

. ├── Cargo.toml # Rust 后端配置 ├── Cargo.lock ├── src/ │ └── lib.rs # Rust 库代码 ├── NativeDictionary/ # SwiftUI 前端 Xcode 项目或包 │ ├── Package.swift # Swift Package Manager 清单 │ ├── Sources/ │ └── Resources/ ├── dictionary-data/ # 词库数据文件(可能为子模块或需单独下载) └── README.md

4. 项目编译与首次运行

拿到源码后,下一步是将其编译成可运行的应用程序。

4.1 编译 Rust 核心库首先,我们需要将 Rust 代码编译成一个静态库(.a)或动态库(.dylib),供 Swift 调用。这通常在项目的Cargo.toml中配置。

# 在 Cargo.toml 中可能需要这样的配置 [lib] name = "dictionary_core" crate-type = ["staticlib"] # 或 ["cdylib"]

进入项目根目录,执行编译:

cargo build --release

--release参数会进行优化,生成性能最佳的二进制文件。编译成功后,库文件会出现在target/release/目录下,例如libdictionary_core.a

4.2 构建并运行 SwiftUI 应用根据项目是使用 Xcode 项目还是纯 Swift Package,步骤略有不同。

情况A:使用 Xcode 项目

# 打开 Xcode 项目 open NativeDictionary/NativeDictionary.xcodeproj

在 Xcode 中:

  1. 在顶部 Scheme 选择器中选择你的目标设备(如 “My Mac”)。
  2. 点击Product->Build(⌘B) 编译。
  3. 编译成功后,点击Product->Run(⌘R) 运行应用。

情况B:使用 Swift Package Manager (SPM)如果项目根目录有Package.swift,则可以直接用命令行构建。

# 在包含 Package.swift 的目录下 swift build -c release # 运行生成的可执行文件(路径可能不同,请根据构建输出确定) ./.build/release/NativeDictionary

更常见的是,Swift Package 会依赖已编译好的 Rust 库,并通过Package.swift中的.linkedLibrary.binaryTarget来链接。具体链接配置是项目工程化的关键,可能需要参考项目的详细构建说明。

4.3 解决常见的编译问题

  • Rust 库链接错误:Swift 找不到 Rust 符号。确保Package.swift或 Xcode 的Build Settings中正确设置了Library Search PathsLink Binary With Libraries,包含了target/release目录和libdictionary_core.a
  • 头文件找不到:Swift 需要知道 Rust 暴露的 C 函数接口。通常项目会使用cbindgen工具从 Rust 代码生成 C 头文件(.h)。确保生成的头文件被引入到 Swift 模块中(通过Bridging-Header.hmodule.modulemap)。
  • 签名问题:在 macOS 上运行本地开发的应用,可能需要在系统设置 -> 隐私与安全性中允许运行未签名的应用,或在 Xcode 中设置临时签名(Team 选择None,但修改 Bundle Identifier)。

5. 核心功能体验与代码浅析

成功运行应用后,让我们从用户和开发者双重视角,看看它提供了哪些核心功能,以及这些功能背后对应的代码逻辑。

5.1 极速搜索与输入响应作为用户,你首先感受到的应该是搜索框的即时响应。输入字符的同时,建议列表或结果页面应实时更新。

  • 前端实现 (SwiftUI)
    // 一个简化的搜索视图示例 struct SearchView: View { @State private var searchText = "" // 假设有一个与 Rust 后端交互的 ViewModel @StateObject private var viewModel = DictionaryViewModel() var body: some View { VStack { TextField("输入单词...", text: $searchText) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() .onChange(of: searchText) { newValue in // 防抖处理,避免过于频繁的查询 viewModel.debouncedSearch(query: newValue) } // 显示结果或建议列表 List(viewModel.results) { result in DefinitionRow(entry: result) } } } } // ViewModel 负责与 Rust 后端通信 class DictionaryViewModel: ObservableObject { @Published var results: [DictionaryEntry] = [] private var workItem: DispatchWorkItem? func debouncedSearch(query: String) { workItem?.cancel() // 取消前一个未完成的查询 let task = DispatchWorkItem { [weak self] in self?.performSearch(query: query) } workItem = task DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: task) // 延迟300毫秒 } func performSearch(query: String) { // 调用 Rust FFI 函数 let entries = rust_search(query) // 假设的 Rust 函数调用 DispatchQueue.main.async { self.results = entries } } }
  • 后端实现 (Rust FFI)
    // 在 lib.rs 中暴露给 C 的接口 use std::ffi::{CStr, CString}; use std::os::raw::c_char; #[no_mangle] pub extern "C" fn search(query: *const c_char) -> *mut c_char { let query_str = unsafe { CStr::from_ptr(query).to_str().unwrap_or("") }; // 调用内部查询引擎 let results = dictionary_engine::search(query_str); // 将结果序列化为 JSON 字符串返回 let json_string = serde_json::to_string(&results).unwrap(); CString::new(json_string).unwrap().into_raw() } // 对应的释放函数,防止内存泄漏 #[no_mangle] pub extern "C" fn free_string(s: *mut c_char) { unsafe { if s.is_null() { return; } let _ = CString::from_raw(s); } }

5.2 美观的释义渲染与排版查询结果的展示质量是词典的灵魂。它需要支持音标、多词性、丰富的例句和样式。

  • SwiftUI 富文本渲染:SwiftUI 的Text视图可以组合不同样式的文本。对于更复杂的 HTML 或自定义标记,可能需要使用AttributedStringNSAttributedString进行解析渲染。
    struct DefinitionRow: View { let entry: DictionaryEntry var body: some View { VStack(alignment: .leading, spacing: 8) { // 单词和音标 HStack { Text(entry.word).font(.headline).bold() Text(entry.phonetic).font(.subheadline).foregroundColor(.gray) } // 词性和释义列表 ForEach(entry.senses) { sense in HStack(alignment: .top) { Text(sense.partOfSpeech) .font(.caption) .padding(.horizontal, 6) .padding(.vertical, 2) .background(Color.blue.opacity(0.2)) .cornerRadius(4) Text(sense.definition) .font(.body) } } // 例句 if let example = entry.example { Text("\"\(example)\"") .font(.body.italic()) .foregroundColor(.secondary) .padding(.top, 4) } } .padding(.vertical, 4) } }

5.3 词库管理与自定义开源项目的优势在于可以自由扩展词库。项目可能支持多种格式。

  • 加载自定义词库:应用可能会在首次启动时,将内置或用户指定的.dictionary(macOS 词典格式)、StarDict等格式的文件,通过 Rust 后端解析并索引到内存中。
  • 配置示例:应用可能提供一个设置界面,让用户添加或移除词库路径。其配置文件可能是一个简单的plistjson文件。
    // ~/Library/Application Support/NativeDictionary/dictionaries.json [ { "name": "牛津高阶英汉双解词典", "path": "~/Dictionaries/Oxford.dictionary", "enabled": true }, { "name": "用户自定义科技词汇", "path": "~/Dictionaries/MyGlossary.json", "enabled": true } ]

6. 高级特性与深度集成探索

一个优秀的原生词典不应只是一个查词窗口,而应深度融入 macOS 生态系统。

6.1 全局快捷键与菜单栏控制实现像Cmd+Ctrl+D这样的全局划词翻译(需辅助功能权限),或是在菜单栏提供一个快速查询入口,可以极大提升效率。

  • SwiftUI 应用生命周期与事件处理:需要在AppDelegate或新的App协议中监听全局事件。
  • 权限配置:在Info.plist中添加Privacy - Accessibility Usage Description键值,以请求辅助功能权限,从而监听系统范围内的键盘和鼠标事件。

6.2 与系统服务的集成

  • 共享扩展 (Share Extension):允许用户在任何选中文本的 App 中,通过系统共享菜单直接查询。
  • 聚焦插件 (Spotlight Importer):为词典内容创建元数据索引,使得用户能在 Spotlight 中直接搜索单词并看到简要释义。
  • 快捷键服务:通过NSUserDefaults保存用户自定义的快捷键,并在应用启动时注册全局热键监听。

6.3 数据同步与生词本利用CloudKit或用户自选的同步服务(如 iCloud Drive),实现生词本、查询历史在多设备间的同步。这是一个能显著增加用户粘性的功能。

7. 常见问题与故障排查指南

在开发、编译或使用过程中,你可能会遇到以下问题。

问题现象可能原因排查方式解决方案
编译错误:Undefined symbol: _rust_searchSwift 链接器找不到 Rust 函数符号。1. 检查cargo build --release是否成功。
2. 检查 Xcode 的Build Settings->Library Search Paths是否包含$(PROJECT_DIR)/../target/release
3. 检查Link Binary With Libraries是否添加了.a文件。
4. 检查生成的 C 头文件是否被正确引入 Bridging Header。
1. 确保 Rust 库的crate-type设置为["staticlib"]
2. 在Package.swift的 target 中正确声明链接依赖。
3. 运行cargo clean后重新构建。
应用启动后立即崩溃FFI 边界数据传递错误,或内存管理问题(如悬垂指针)。1. 在 Xcode 中运行,查看崩溃堆栈信息。
2. 检查 Rust 函数返回的字符串是否以\0结尾。
3. 检查 Swift 端是否正确调用free_string释放内存。
1. 确保 Rust 端返回的CString使用into_raw(),Swift 端接收后最终调用对应的释放函数。
2. 使用更安全的桥接库,如UniFFI或手动封装@_cdecl函数。
搜索无结果或结果错误词库文件路径错误、格式不支持或索引构建失败。1. 检查应用日志(Console.app)。
2. 确认词库文件是否存在且有读取权限。
3. 在 Rust 后端添加详细日志,输出索引加载和查询过程。
1. 将词库文件放在应用沙盒的Application Support目录或用户指定的可访问目录。
2. 验证词库解析器是否能正确处理你的词库文件格式。
界面显示异常(白屏、错位)SwiftUI 视图层级或状态管理问题,或 macOS 版本不兼容。1. 检查 Xcode 的预览 (Canvas) 是否正常。
2. 简化视图,逐步排查是哪个组件导致问题。
3. 检查是否使用了仅在新版 macOS 可用的 API。
1. 使用@State,@ObservedObject等正确管理状态。
2. 使用if #available(macOS ...)进行 API 可用性检查。
3. 明确项目支持的最低 macOS 版本。
无法启用全局快捷键辅助功能权限未授予。前往系统设置 -> 隐私与安全性 -> 辅助功能,检查你的应用是否在列表中并已勾选。1. 在应用中主动提示用户去开启权限。
2. 确保Info.plist中已正确声明权限用途描述。

8. 最佳实践与项目贡献指南

如果你希望长期使用或为此项目贡献代码,以下建议能帮助你更高效地协作。

8.1 开发最佳实践

  • 代码格式化:在 Rust 端使用cargo fmt,在 Swift 端使用swift-format或 Xcode 的自动格式化功能,保持代码风格统一。
  • 错误处理:Rust 端应使用Result<T, E>妥善处理错误,并通过 FFI 边界将错误信息传递到 Swift 端,而不是直接 panic。Swift 端也应做好错误展示。
  • 资源管理:词库文件可能很大。考虑在 Rust 端使用内存映射文件 (memmap) 或惰性加载策略,避免启动时占用过多内存。
  • 测试:为 Rust 的核心查询算法编写单元测试 (cargo test)。为 SwiftUI 的 ViewModel 编写单元测试,并为关键的用户交互流程编写 UI 测试。

8.2 如何贡献词库或功能

  1. Fork 与分支:在 GitHub 上 Fork 原项目,并基于main分支创建功能分支(如feat/add-chinese-dictionary)。
  2. 词库贡献
    • 确保词库文件不侵犯版权,最好是开源或已获授权的资源。
    • 提供词库的清晰来源说明。
    • 如果可能,提供将源数据转换为项目所支持格式的脚本或说明。
  3. 代码贡献
    • 先查看项目的CONTRIBUTING.md文件(如果有)。
    • 确保新功能有对应的测试。
    • 更新相关的文档(如README.md)。
  4. 提交 Pull Request (PR):在 PR 中清晰描述你的改动目的、测试情况以及可能对现有功能的影响。

8.3 性能优化建议

  • Rust 索引优化:对于大型词库,考虑使用更高效的数据结构,如FST(有限状态转换器) 或Trie树进行前缀搜索,用倒排索引进行全文搜索。
  • SwiftUI 视图优化:对于超长的释义列表,使用LazyVStackList进行懒加载,避免一次性渲染所有内容导致界面卡顿。
  • 缓存策略:对频繁查询的单词结果进行内存缓存,可以设置合理的缓存大小和过期策略。

通过这个开源项目,我们看到的不仅是一个更美观、更快速的词典工具,更是一个如何利用现代技术栈(Rust + SwiftUI)来重塑经典系统应用的绝佳案例。它证明了,即使是查单词这样“简单”的需求,也有巨大的体验优化空间和技术实践价值。

对于用户而言,你可以立即尝试编译和使用它,获得远超系统原生的查词体验。对于开发者而言,这是一个学习跨语言(Rust/Swift)交互、高性能数据处理和现代 macOS 应用开发的宝贵资源。项目的开源性质,意味着你有机会亲手为它添加想要的功能,或是借鉴其架构思路,用于你自己的下一个 macOS 原生应用创意。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 13:55:26

Kronos 金融K线预测快速上手指南:从装环境到喂数据微调

Kronos 金融K线预测快速上手指南&#xff1a;从装环境到喂数据微调 【免费下载链接】Kronos Kronos: A Foundation Model for the Language of Financial Markets 项目地址: https://gitcode.com/GitHub_Trending/kronos14/Kronos Kronos 是一个专门做金融K线预测的开源…

作者头像 李华
网站建设 2026/9/2 13:49:46

基于STM32与FreeRTOS的六自由度机械臂:总线舵机与PS2手柄控制实战

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

作者头像 李华
网站建设 2026/9/2 13:49:38

非全日制MBA边工作边写论文,按项目节点推进的节奏

非全MBA的论文焦虑&#xff0c;很少来自选题本身&#xff0c;更多来自工作、课程、论文三线并行下「不知道进度到哪了」。把论文拆成一个个项目节点&#xff0c;用周中碎片喂进度、用周末整块出成果&#xff0c;是不少在职学员验证过的推进方式。知学术AIPaperGPT 的免费智能大…

作者头像 李华
网站建设 2026/9/2 13:48:11

架构不止是画线:玄武架构的连接约束与治理之道

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

作者头像 李华
网站建设 2026/9/2 13:46:41

游戏汉化技术全解析:从资源解包到UI适配的工程实践

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

作者头像 李华
网站建设 2026/9/2 13:46:26

DVWA靶场实战指南:从SQL注入到XSS的Web安全攻防全解析

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

作者头像 李华