SuperAPI AI 工具集

obsidian-plugin-development

编程与开发

查看项目

更新于 10-15 10:48

技能介绍

用途

本技能用于编写能一次性通过 Obsidian 插件自动审核的 Obsidian 插件代码。自 2026 年 5 月起,插件通过 community.obsidian.md 社区中心提交与审核(旧的、由 ObsidianReviewBot 把关的 obsidianmd/obsidian-releases PR 流程已退役);每次 GitHub 发布都会被自动扫描。以下规则均通过 eslint-plugin-obsidianmd 与 @typescript-eslint 强制执行。标注为“必需”的违规会阻止审核通过,已上架插件的不合格版本将在 24 小时内从目录搜索中移除。

何时使用

  • 编写或编辑 Obsidian 插件中的 TypeScript 代码
  • 在 community.obsidian.md 仪表盘准备插件提交
  • 修复自动审核违规
  • 添加 UI 文本、命令、设置标签页或 DOM 操作

必需规则速查

  1. 所有 UI 文本使用句子大小写:每个用户可见字符串仅用句子大小写,例如 'Block settings'、'Add block'、'Recent files'。适用于 Setting.setName()、Setting.setDesc()、createEl() 文本、按钮标签、模态标题、通知、菜单项、工具提示。专有名词与缩写(如 API、GitHub、Obsidian)保留原大小写。

  2. 禁止内联样式:绝不直接赋值 element.style.*,改用 CSS 类。例如用 el.addClass('hp-flex-container') 替代 el.style.display = 'flex';动态 CSS 自定义属性用 setCssProps 或 setCssStyles。被标记的属性包括 display、transform、opacity、width、height、margin、padding、cursor、fontSize、fontFamily、flexDirection、alignItems、flexShrink、borderRadius、backdropFilter、background、borderWidth、borderStyle、transition、gridTemplateRows、transformOrigin 等。

  3. 禁止不必要的类型断言:当断言不改变类型时不要用 as Type。例如 'draft.url as string ?? '''' 应改为 String(draft.url ?? '''')。

  4. 必须处理 Promise:每个 Promise 必须被 await、.catch()、带拒绝处理的 .then(),或 void 处理。

  5. 无 await 不要用 async:移除未使用 await 的方法上的 async。

  6. 期望 void 处不要返回 Promise:不要在期望 void 的回调中返回 Promise。

  7. 禁止对象字符串化:确保值不会字符串化为 [object Object],必要时用 String() 包裹。

  8. 设置标题使用 Setting API:不要创建 HTML 标题,用 Setting.setHeading()。例如用 new Setting(contentEl).setName('My settings').setHeading() 替代 contentEl.createEl('h2', ...)。

  9. onunload 中不要 detachLeaves:Obsidian 会自动处理 leaf 清理,手动 detach 会重置用户布局。

  10. 禁止 TFile/TFolder 强制转换:用 instanceof 而非类型转换。

  11. 禁止使用违禁 DOM 元素:不要动态创建 <style> 或 <link> 元素。

  12. 插件不能作为组件:不要把 this(插件)传给 MarkdownRenderer.render(),应使用 Component 实例。

  13. 插件中不要存储视图引用:不要在插件属性中存储视图引用(内存泄漏)。

  14. 使用 Vault.configDir:不要硬编码 .obsidian,用 this.app.vault.configDir。

  15. 平台检测:使用 Platform API,而非 navigator.userAgent。例如 import { Platform } from 'obsidian'; if (Platform.isMacOS) { ... }。

  16. 禁止正则后行断言:后行断言在某些 iOS 版本会出错,除非设 isDesktopOnly: true,否则避免使用。

  17. 命令:命令 ID 或名称中不得含词 “command”;命令 ID 中不得含插件 ID;命令名称中不得含插件名;不得设默认快捷键。

  18. 文件操作:使用 FileManager.trashFile() 而非 Vault.trash()/Vault.delete();不要遍历所有文件按路径查找,用 getAbstractFileByPath();用户提供的路径用 normalizePath() 处理。

  19. 禁止示例/模板代码:移除 obsidian-sample-plugin 中的 MyPlugin、SampleModal 模板代码。

  20. Object.assign:不要用 Object.assign(this.settings, data) 修改默认值。

  21. Manifest 与许可证:manifest.json 必须有有效结构;LICENSE 必须有正确的版权持有者与当前年份;插件 ID 为字母数字加连字符,不含 “obsidian”,无 “plugin” 后缀;描述中不含 “Obsidian”、不含 “This plugin”,且必须以 . ? ! ) 结尾。

  22. 弹出窗口兼容性:使用弹出窗口安全的全局变量。裸 document 与全局定时器只针对主窗口,在弹出窗口会失效。应使用 activeDocument、activeWindow.setTimeout 等。相关规则:obsidianmd/prefer-active-doc(警告)、obsidianmd/prefer-window-timers(错误,也涵盖 setInterval 和 requestAnimationFrame)、obsidianmd/no-global-this(错误)。

  23. API 可用性与 minAppVersion:代码调用的每个 API 必须存在于 manifest 声明的 minAppVersion 中。obsidianmd/no-unsupported-api 会对照 manifest 交叉检查用法。

  24. 使用 createEl 辅助方法:用 createEl('iframe')、createFragment()、el.createSpan({ text: file.path }) 替代 document.createElement('iframe')、document.createDocumentFragment()、el.createEl('span', { text: file.path })。规则 obsidianmd/prefer-create-el 由审核平台执行,但尚未进入 npm 版本,本地 ESLint 会漏检,需手动检查这些模式。

  25. ESLint 指令注释:每条 eslint-disable 指令都需要描述,且部分规则完全不可禁用(obsidianmd/no-static-styles-assignment、obsidianmd/ui/sentence-case)。需添加原因描述,如 // eslint-disable-next-line obsidianmd/prefer-active-doc -- main-window-only startup code。平台侧通过 @eslint-community/eslint-comments/require-description 与 no-restricted-disable 强制执行;建议在本地添加同一插件以便发布前捕获。

可选(非阻断)

  • 移除未使用的变量与导入
  • 使用 AbstractInputSuggest 而非复制的 TextInputSuggest
  • 不要在 localStorage 或 sessionStorage 中持久化数据。插件数据用 Plugin.loadData()/saveData()(存于插件的 data.json,随 vault 同步)。设备特定的每 vault 值(窗口位置、设备缓存)用 App.loadLocalStorage()/App.saveLocalStorage(),这类键会被限定到当前 vault。裸 localStorage 在同一设备的所有 vault 间共享且从不同步;sessionStorage 重启后不保留。

额外最佳实践

  • 禁止 innerHTML/outerHTML:用 createEl、setText、sanitizeHTMLToDom
  • 网络请求用 requestUrl() 而非 fetch()
  • 主题化使用 CSS 变量:--background-secondary、--text-muted 等
  • CSS 作用域:所有插件 CSS 限定在插件容器内
  • 无障碍:图标按钮加 aria-label、键盘导航、焦点指示
  • 触控目标:移动端最小 44x44px
  • 自动清理:registerEvent()、registerInterval()、register()
  • 无生产日志:onload()/onunload() 中不要有 console.log

API 参考

参见本技能目录中的 references/obsidian-api-reference.md,其中包含精简的 TypeScript API 参考,覆盖 Plugin、App、Vault、Workspace、MetadataCache、FileManager、Component、View、Modal、Setting、Menu、MarkdownRenderer、Platform、DOM 辅助方法等所有关键类。完整类型定义请阅读项目中的 node_modules/obsidian/obsidian.d.ts。

本地运行检查

npm install --save-dev eslint-plugin-obsidianmd @eslint-community/eslint-plugin-eslint-comments

配置 ESLint,使用 obsidianmd 推荐配置加上 eslint-comments 规则(require-description、no-restricted-disable)。对 src/ 和 package.json 都进行 lint(依赖规则只在 lint package.json 时触发)。发布前运行:平台会自动扫描每次 GitHub 发布。

常见错误与修复

  • UI 文本用标题大小写 → 全部改为句子大小写
  • el.style.display = 'none' → el.addClass('hp-hidden')
  • as string ?? '''' → String(x ?? '''') 这句的原文是 'as string ?? '' 与 String(x ?? '')' 的对照,注意处理
  • 模态中用 createEl('h2', ...) → new Setting(el).setName(...).setHeading()
  • 无 await 的 async onOpen → 移除 async 关键字
  • 未处理的 promise → 添加 void、await 或 .catch()
  • onunload 中的 detachLeavesOfType → 移除,Obsidian 会自动处理
  • abstractFile as TFile → if (x instanceof TFile)
  • setTimeout(cb, ms) → activeWindow.setTimeout(cb, ms)
  • document.body → activeDocument.body
  • el.createEl('span', {...}) → el.createSpan({...})
  • 裸 // eslint-disable-next-line → 追加 -- 原因描述
  • 新 API 搭配旧 minAppVersion → 将 minAppVersion 提升至该 API 的版本
  • localStorage.setItem('key', v) → this.app.saveLocalStorage('key', v) 或 this.saveData(...)

限制

资料未提供上述规则之外的更多功能、收费或价格信息。