mcpField
O
obsidian

obsidian

github.com/gapmiss开发195477 次安装3 周前更新
代码仓库

关于 obsidian

面向 Obsidian 插件开发者的开发规范技能,汇总 ESLint 规则与提交要求,涵盖生命周期、API、界面、兼容性和无障碍实践,并提供新插件脚手架生成器。

主要功能

  1. 01生成最小插件脚手架
  2. 02涵盖提交与命名规则
  3. 03指导生命周期与 API 使用
  4. 04提供 UI、样式及无障碍规范

适用场景

  1. 01创建 Obsidian 插件项目
  2. 02检查插件代码及提交流程
  3. 03处理设置、文件和事件实践

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/gapmiss/obsidian-plugin-skill --skill obsidian

安装后在 Claude Code 中输入/obsidian确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference/
accessibility.md
code-quality.md
community-scanner.md
css-styling.md
eslint-setup.md
file-operations.md
memory-management.md
submission.md
type-safety.md
ui-ux.md

详细介绍

Obsidian 插件开发指南

请遵循以下综合指南,这些指南基于官方 Obsidian ESLint 插件规则、提交要求和最佳实践。

入门

快速入门工具

对于新的插件项目,可以使用交互式样板生成器:

  • 脚本:技能仓库中的 tools/create-plugin.js
  • 命令:使用 Agent 的调用方式(/create-plugin、$create-plugin 或 @create-plugin)调用 create-plugin
  • 生成精简且符合最佳实践的样板代码,不包含示例代码
  • 检测现有项目,并且只添加缺少的文件

当用户询问如何创建新插件、想要启动新项目或需要帮助搭建基本结构时,建议使用样板生成器。


规则参考(eslint-plugin-obsidianmd v0.4.2)

提交与命名
#规则✅ 应做❌ 不应做
1插件 ID省略“obsidian”;不要以“plugin”结尾包含“obsidian”或以“plugin”结尾
2插件名称省略“Obsidian”;不要以“Plugin”结尾包含“Obsidian”或以“Plugin”结尾
3插件名称不要以“Obsi”开头或以“dian”结尾以“Obsi”开头或以“dian”结尾
4描述省略“Obsidian”、“This plugin”等字样使用“Obsidian”或“This plugin”
5描述以 .?!) 标点结尾描述末尾不加标点
内存与生命周期
#规则✅ 应做❌ 不应做
6事件清理使用 registerEvent() 自动清理注册事件但不清理
6aDOM 事件在插件或所属组件上使用 registerDomEvent()将 addEventListener 与手动 removeEventListener 清理配对使用
7视图引用直接返回视图/组件将视图引用存储在插件属性中,或将插件作为组件传递给 MarkdownRenderer
8叶子分离让 Obsidian 处理叶子清理在 onunload 中调用 detachLeavesOfType()
类型安全
#规则✅ 应做❌ 不应做
9TFile/TFolder使用 instanceof 进行类型检查强制转换为 TFile/TFolder;使用 any;使用 var
10DOM instanceof对 DOM 节点/UIEvents 使用 .instanceOf(T)使用 instanceof 进行跨窗口 DOM 检查
UI/UX
#规则✅ 应做❌ 不应做
11UI 文本使用句首字母大写格式——“Advanced settings”;专有名称使用 acronyms/brands 选项使用标题式大小写——“Advanced Settings”
12JSON 本地化文件在 JSON 本地化文件中使用句首字母大写格式(recommendedWithLocalesEn)在本地化 JSON 中使用标题式大小写
13TS/JS 本地化文件在 TS/JS 本地化模块中使用句首字母大写格式在本地化模块中使用标题式大小写

注意(v0.4.0): ui/sentence-case 现已启用(warn),并会检查内联 UI 字符串;它在 v0.3.0 中处于禁用状态。使用 recommendedWithLocalesEn 配置还可以检查英文本地化文件(规则 12–13)。 | 14 | 命令名称 | 在命令名称/ID 中省略“command” | 在名称/ID 中包含“command” | | 15 | 命令 ID | 在命令 ID/名称中省略插件 ID/名称 | 在命令 ID 中重复插件 ID | | 16 | 热键 | 不设置默认热键 | 设置默认热键 | | 17 | 设置标题 | 使用 .setHeading() | 手动创建 HTML 标题;在标题中使用“General”、“settings”或插件名称 |

声明式设置(1.13.0+)

recommended 中的全部 4 条 settings-tab 规则均以 warn 级别提供。规则 17a/17c/17d 会从 manifest.json 读取 minAppVersion;17b 不受版本限制。

#规则✅ 应做❌ 不应做
17asettings-tab/require-display当 minAppVersion < 1.13.0 时保留 display()仅提供声明式设置,导致旧版 Obsidian 上无法渲染任何内容
17bsettings-tab/prefer-setting-definitions为每个 PluginSettingTab 实现 getSettingDefinitions()仅依赖 display()——设置不会出现在 1.13+ 的全局搜索中
17csettings-tab/prefer-update-over-display调用 this.update() 重新渲染声明式设置调用 this.display()——当定义非空时会绕过它
17dsettings-tab/no-deprecated-display当 minAppVersion >= 1.13.0 且定义存在时删除 display()留下无用的 display()(可自动修复)
—设置数据将所有持久化数据保存在 plugin.settings 中通过 saveData() 存储同级键——自动持久化会覆盖它们

检测注意事项: 这些规则仅匹配直接 extends PluginSettingTab 的情况。extends obsidian.PluginSettingTab 不在检测范围内,不会被标记——但相关指导仍然适用。

API 最佳实践
#规则✅ 应做❌ 不应做
18活动文件编辑使用 Editor API使用 Vault.modify() 编辑活动文件
19后台文件修改使用 Vault.process()使用 Vault.modify() 进行后台修改
20文件删除使用 FileManager.trashFile()直接使用 Vault.trash() 或 Vault.delete()
21文件查找使用 Vault.getAbstractFileByPath()使用 Vault.getFiles().find() 遍历所有文件
22用户路径使用 normalizePath()将 .obsidian 路径硬编码;使用未经处理的用户路径
23操作系统检测使用 Platform API使用 navigator.platform/userAgent
24网络请求使用 requestUrl()使用 fetch()
24a响应类型处理解析 response.text,并在 API 边界处验证/转换一次将 response.json(类型为 any)传入插件代码
25日志记录尽量减少控制台日志;生产环境中不要在 onload/onunload 中记录日志在 onload/onunload 中使用 console.log
26输入建议使用内置的 AbstractInputSuggest复制 Liam 的 TextInputSuggest 实现
27API 兼容性检查 API 是否可用的 minAppVersion(例如,getSettingDefinitions() 需要 1.13.0)使用声明的 minAppVersion 中不可用的 API
28语言检测使用 Obsidian 的 getLanguage()使用 localStorage.getItem('language') 或 i18next-browser-languagedetector
弹出窗口兼容性
#规则✅ 应做❌ 不应做
29文档/窗口使用 activeDocument 和 activeWindow使用全局 document 和 window
29aGetter 捕获如果之后需要使用同一文档,请将 activeDocument 捕获到变量中在设置时和清理时分别调用 activeDocument——它会随焦点变化,可能返回不同的文档
30定时器使用 window.setTimeout()、window.setInterval()、window.requestAnimationFrame() 等(规则 29 的例外)使用裸露的 setTimeout()/setInterval(),或使用 activeWindow.setTimeout()——prefer-window-timers 会标记这两种写法
31主工作区 UI从设置中使用 this.app.workspace.containerEl.ownerDocument从设置窗口使用 activeDocument 更新主工作区

注意(v0.4.0): prefer-active-doc 默认仍处于禁用状态——它是唯一以 off 状态发布的 Obsidian 规则。要支持弹出窗口,请手动启用它。

注意(v1.13.0): 设置现已在新窗口中打开。设置回调中的 activeDocument 指向设置窗口,而非主库。要定位主工作区 UI,请使用 this.app.workspace.containerEl.ownerDocument。

注意: activeDocument/activeWindow 是动态 Getter,会跟踪当前聚焦的窗口。在设置时通过 activeDocument.addEventListener() 添加的监听器,在清理时无法可靠地通过 activeDocument.removeEventListener() 移除。建议使用 registerDomEvent()(规则 6a),它会在注册时捕获目标。

事件处理
#规则✅ 应做❌ 不应做
31编辑器拖放/粘贴检查 evt.defaultPrevented,并调用 evt.preventDefault()不检查 defaultPrevented 就处理编辑器拖放/粘贴
样式
#规则✅ 应做❌ 不应做
32CSS 变量所有样式都使用 Obsidian CSS 变量将颜色、尺寸或间距硬编码
33CSS 作用域将 CSS 限定在插件容器内使用范围过广的 CSS 选择器
34样式元素使用 styles.css 文件(no-forbidden-elements)创建 <link> 或 <style> 元素;通过 JavaScript 赋予样式
34a!important提高选择器优先级或使用 CSS 变量使用 !important——这会覆盖用户主题/代码片段
34b:has 选择器条件变化时通过 TypeScript 切换类使用 :has——这会导致选择器大范围失效并引发性能问题
安全性与兼容性
#规则✅ 应做❌ 不应做
35DOM 创建通过 prefer-create-el 使用 Obsidian DOM 辅助方法(createEl()、createDiv()、createSpan()、createSvg()、createFragment());代码检查器会自动将 activeDocument.createElement() 修正为 activeWindow.createEl()(v0.4.1)使用 document.createElement()、document.createDocumentFragment() 等
36Node.js 模块使用 Platform.isDesktop 检查保护 Node.js 导入(no-nodejs-modules)不进行平台检查就导入 Node.js 模块
37iOS 兼容性避免使用正则表达式后行断言(与 iOS < 16.4 不兼容)使用正则表达式后行断言
无障碍(强制要求)
#规则✅ 应做❌ 不应做
38键盘访问确保所有交互元素都可通过键盘访问;可使用 Tab 遍历所有元素创建无法访问的交互元素
39ARIA 标签为图标按钮提供 ARIA 标签;使用 data-tooltip-position 设置工具提示使用没有 ARIA 标签的图标按钮
40焦点指示器使用带有 Obsidian CSS 变量的 :focus-visible;触控目标 ≥ 44×44px移除焦点指示器;使触控目标 < 44×44px
代码质量
规则✅ 应该做❌ 不应该做
示例代码删除所有示例/模板代码保留 MyPlugin、SampleModal 这类类名
Object.assign使用 Object.assign({}, defaults, overrides)(object-assign)使用 Object.assign(defaultsVar, other)——这会修改 defaults
LICENSE版权持有者不能是 "Dynalist Inc.";年份必须是当前年份(validate-license)将 "Dynalist Inc." 留作版权持有者,或使用过时的年份
异步操作使用 async/await使用 Promise 链
已弃用的包将被标记的 npm 包替换为 Node.js 内置模块(例如,builtin-modules → import { builtinModules } from "node:module")使用扫描器标记为可替换的包

详细指南

有关特定主题的完整信息,请参阅参考文件:

内存管理与生命周期
  • 使用 registerEvent()、addCommand()、registerDomEvent()、registerInterval()
  • registerDomEvent() 与手动使用 addEventListener 的区别(以及 activeDocument 漂移错误)
  • 避免在插件中引用视图
  • 不要将插件用作组件
  • 正确清理 leaf
类型安全
  • 使用 instanceof,而不是类型转换
  • 避免使用 any 类型
  • 使用 const 和 let,而不是 var
UI/UX 标准
  • 强制使用句首字母大写格式(TypeScript、JSON locale、TS/JS locale 模块)
  • 使用 recommendedWithLocalesEn 配置检查 locale 文件
  • 命令命名约定(不包含 "command"、插件名称或插件 ID)
  • 设置和配置最佳实践
  • 声明式设置 API(1.13+):迁移路径、控件类型、注意事项
文件与库操作
  • 视图访问模式
  • Editor API 与 Vault API
  • 原子文件操作
  • 文件管理
  • 路径处理
CSS 样式最佳实践
  • 避免使用内联样式
  • 使用 Obsidian CSS 变量
  • 避免使用 !important(改用选择器特异性或 CSS 变量)
  • 避免使用 :has 选择器(改为通过 TypeScript 切换类)
  • 限定插件样式的作用范围
  • 主题支持
  • 间距与布局
无障碍(A11y)
  • 键盘导航(必须)
  • ARIA 标签和角色(必须)
  • 工具提示与无障碍
  • 焦点管理(必须)
  • 焦点可见样式(必须)
  • 屏幕阅读器支持(必须)
  • 移动设备和触控无障碍(必须)
  • 无障碍检查清单
代码质量与最佳实践
  • 删除示例代码
  • 安全最佳实践
  • 平台兼容性
  • API 使用最佳实践
  • async/await 模式
  • DOM 辅助工具
  • 已弃用/可替换的包(例如,builtin-modules → node:module)
插件提交要求
  • 仓库结构
  • 提交流程
  • 语义化版本控制
  • 测试检查清单
  • 补充资源与重要说明
社区插件扫描器
  • 扫描器运行的内容(ESLint 规则集及 ESLint 之外的检查)
  • 评分卡系统(健康状况、审核、信息披露、改进建议)
  • 标注版本——随着扫描器迭代,只需更新这一个文件
ESLint 设置指南
  • 符合社区扫描器要求的完整 ESLint 配置
  • 为什么必须使用 typescript-eslint recommendedTypeChecked
  • 常见违规及修复方法(未处理的 Promise、require 导入等)
  • 弹出窗口兼容性规则

插件提交验证工作流

提交插件前,请按以下顺序操作:

  1. 运行 ESLint — 使用 eslint-plugin-obsidianmd 执行 npx eslint .;修复所有错误和警告(警告会影响你的评分卡)
  2. 验证清单文件 — 确认 id、name、description、version 和 minAppVersion 符合命名和格式规则(规则 1–5)
  3. 检查 LICENSE — 版权持有者不能是 "Dynalist Inc.",年份必须是当年
  4. 在移动设备上测试 — 确认没有正则表达式后行断言、没有 fetch(),且触控目标 ≥ 44×44px(仅当插件声明为仅支持桌面时可跳过)
  5. 键盘无障碍审查 — 使用 Tab 键遍历所有交互元素;确认存在焦点指示器和 ARIA 标签
  6. 创建 GitHub Release — 标签必须与 manifest.json 中的版本一致;附上 main.js、manifest.json 和 styles.css(可选)
  7. 通过 community.obsidian.md 提交 — 登录,关联 GitHub 账号,前往 Plugins → New plugin,输入仓库 URL,查看开发者政策,然后提交

如果修复后 ESLint 报告了新错误,请从第 1 步重新运行。


评分卡系统

已发布的插件会获得一个显示在 community.obsidian.md 上的评分卡。评分卡会影响用户信任度和可发现性——评分较低会阻碍用户安装。

要点:

  • 总分目标为 90% 以上
  • 修复所有 ESLint 警告,而不仅仅是错误——警告会公开显示
  • 使用 typescript-eslint/recommendedTypeChecked 进行类型感知检查
  • 为发布版本添加 GitHub 工件证明

有关扫描器检查、健康度指标、审查检查、常见警告和改进建议的完整详情,请参阅 社区插件扫描器。


审查/编写代码时

使用此清单进行代码审查和实现:

  1. 内存管理:组件和视图是否得到了妥善管理?
  2. 类型安全:是否使用 instanceof 而不是类型转换?
  3. UI 文本:所有文本是否都使用句式大小写?
  4. 命令命名:是否没有多余的词?
  5. 文件操作:是否使用了推荐的 API?
  6. 移动设备兼容性:是否没有与 iOS 不兼容的功能?
  7. 示例代码:是否已移除所有样板代码?
  8. 清单文件:版本是否正确,结构是否有效?
  9. 无障碍性:是否支持键盘导航,是否有 ARIA 标签和焦点指示器?
  10. 测试:不使用鼠标也能使用插件吗?
  11. 触控目标:所有交互元素是否至少为 44×44px?
  12. 焦点样式:是否使用了 :focus-visible 和合适的 CSS 变量?
  13. 设置:在 1.13+ 上是否使用声明式 getSettingDefinitions()?所有已保存的数据是否都集中在 plugin.settings 中?

常见模式

正确注册命令
// ✅ CORRECT
this.addCommand({
  id: 'insert-timestamp',
  name: 'Insert timestamp',
  editorCallback: (editor: Editor, view: MarkdownView) => {
    editor.replaceSelection(new Date().toISOString());
  }
});
安全缩窄类型
// ✅ CORRECT
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof TFile) {
  // TypeScript now knows it's a TFile
  await this.app.vault.read(file);
}
支持键盘操作的按钮
// ✅ CORRECT
const button = containerEl.createEl('button', {
  attr: {
    'aria-label': 'Open settings',
    'data-tooltip-position': 'top'
  }
});
button.setText('⚙️');

button.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    performAction();
  }
});
声明式设置(1.13+)
// ✅ CORRECT — getSettingDefinitions() replaces display() on 1.13+
getSettingDefinitions() {
  return [
    {
      name: 'Mode',
      control: {
        type: 'dropdown',
        key: 'mode',
        defaultValue: 'fast',
        options: { fast: 'Fast', thorough: 'Thorough' },
      },
    },
  ];
}
主题化 CSS
/* ✅ CORRECT */
.my-plugin-modal {
  background: var(--modal-background);
  color: var(--text-normal);
  padding: var(--size-4-4);
  border-radius: var(--radius-m);
  font-size: var(--font-ui-medium);
}

.my-plugin-button:focus-visible {
  outline: 2px solid var(--interactive-accent);
  outline-offset: 2px;
}

协助开发 Obsidian 插件时,请主动应用这些规则,并根据这些指南提出改进建议。有关具体主题的完整信息,请参阅详细参考文件。

评论

支持 Markdown