本页列出了插件作者在提交插件时收到的常见评审意见。
虽然此页面上的指南是建议,但根据其严重程度,我们可能仍会要求您解决违规问题。
插件开发者政策
Make sure that you've read our Developer policies as well as the Submission requirements for plugins.
请务必阅读我们的[[开发者政策]]以及[[插件的提交要求]]。
常规
避免使用全局应用实例
避免使用全局应用对象 app
(或 window.app
)。请改用插件实例提供的引用 this.app
。
全局应用对象用于调试目的,将来可能会被删除。
UI 文本
本部分列出了在用户界面中设置文本格式的准则,例如设置、命令和按钮。
下面的“设置”→“外观”中的示例演示了用户界面中文本的准则。
有关Obsidian编写和格式化文本的更多信息,请参阅我们的样式指南。
仅当有多个部分时,才使用设置下的标题。
避免在设置选项卡中添加顶级标题,例如“常规”、“设置”或插件名称。
如果设置下有多个部分,并且其中一个包含常规设置,请将它们保留在顶部,而不添加标题。
例如,查看“设置”→“外观”下的设置。
避免在设置标题中使用“设置”
在设置选项卡中,您可以添加标题来组织设置。避免在这些标题中包含“设置”一词。由于设置选项卡下的所有内容都是设置,因此对每个标题重复它变得多余。
- 首选“高级”而不是“高级设置”。
- 首选“模板”而不是“模板设置”。
在UI中使用句子大小写
UI 元素中的任何文本都应使用句子大小写而不是标题大小写,其中只有句子中的第一个单词和专有名词应大写。
- 首选 "Template folder location" 而不是 "Template Folder Location"。
- 首选 "Create new note" 而不是 "Create New Note"。
安全
避免 innerHTML
和 outerHTML
insertAdjacentHTML
从用户定义的输入构建 DOM 元素,使用 innerHTML
和 outerHTML
insertAdjacentHTML
可能会带来安全风险。
下面的示例使用包含用户输入的字符串生成 DOM 元素 ${name}
。 name
可以包含其他 DOM 元素,例如 <script>alert()</script>
,并可允许潜在攻击者在用户计算机上执行任意代码。
function showName(name: string) {
let containerElement = document.querySelector('.my-container');
// DON'T DO THIS
containerElement.innerHTML = `<div class="my-class"><b>Your name is: </b>${name}</div>`;
}
请改用 DOM API 或Obsidian帮助程序函数(如 createEl()
) createDiv()
createSpan()
以编程方式生成 DOM 元素。有关更多信息,请参阅 [[HTML 元素]]。
资源管理
插件卸载时清理资源
插件创建的任何资源(例如事件侦听器)都必须在插件卸载时销毁或释放。
如果可能,请使用 registerEvent() 或 addCommand() 等方法在插件卸载时自动清理资源。
export default class MyPlugin extends Plugin {
onload() {
this.registerEvent(this.app.vault.on("create", this.onCreate));
}
onCreate: (file: TAbstractFile) => {
// ...
}
}
[!NOTE]
您无需清理在插件卸载时删除的资源。例如,如果在 DOM 元素上注册
mouseenter
侦听器,当该元素超出范围时,将对事件侦听器进行垃圾回收。
不要在onunload
分离 leaves
当用户更新您的插件时,任何打开的叶子都将在其原始位置重新初始化,无论用户将它们移动到何处。
命令
避免为命令设置默认热键
设置默认热键可能会导致插件之间发生冲突,并可能覆盖用户已配置的热键。
选择所有操作系统上都可用的默认热键很困难。
对命令使用适当的回调类型
在插件中添加命令时,请使用适当的回调类型。
- 如果命令无条件运行,则使用
callback
。 - 如果命令仅在特定条件下运行,则使用
checkCallback
。
如果该命令需要打开且处于活动状态的 Markdown 编辑器,请使用 editorCallback
或相应的 editorCheckCallback
.
工作区
避免直接访问 workspace.activeLeaf
如果要访问活动视图,请改用 getActiveViewOfType() :
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
// getActiveViewOfType will return null if the active view is null, or if it's not a MarkdownView.
if (view) {
// ...
}
如果要访问活动笔记中的编辑器,请改用 activeEditor
:
const editor = this.app.workspace.activeEditor;
避免管理对自定义视图的引用
管理对自定义视图的引用可能会导致内存泄漏或意外后果。
不要:
this.registerViewType(MY_VIEW_TYPE, () => this.view = new MyCustomView());
请改为执行此操作:
this.registerViewType(MY_VIEW_TYPE, () => new MyCustomView());
要从插件访问视图,请使用 Workspace.getActiveLeavesOfType()
:
for (let leaf of app.workspace.getActiveLeavesOfType(MY_VIEW_TYPE)) {
let view = leaf.view;
if (view instanceof MyCustomView) {
// ...
}
}
库
首选编辑器 API,而不是 Vault.modify
如果要编辑活动注释,请使用 Editor 接口,而不是 Vault.modify()。
编辑器会维护有关活动笔记的信息,例如光标位置、选择和折叠内容。当您使用 Vault.modify() 编辑注释时,所有这些信息都会丢失,这会导致用户体验不佳。
在对笔记的某些部分进行小的更改时,编辑器也更有效率。
仅当您在后台编辑文件时,才使用 Vault.modify()。
首选 Vault API 而不是适配器 API
Obsidian公开了两个用于文件操作的 API:Vault API ( app.vault
) 和适配器 API ( app.vault.adapter
)。
虽然许多开发人员通常更熟悉适配器 API 中的文件操作,但与适配器相比,Vault API 有两个主要优势。
- 性能:Vault API 具有一个缓存层,当Obsidian文件已知时,该缓存层可以加快文件读取速度。
- 安全性:Vault API 按顺序执行文件操作,以避免出现任何争用条件,例如在读取同时写入的文件时。
避免遍历所有文件,建议按路径查找文件
遍历文件是低效的,特别是对于大型库。请改用 getAbstractFileByPath() 。
不建议:
vault.getAllFiles().find(file => file.path === filePath)
请改为执行此操作:
const filePath = 'folder/file.md';
const file = app.vault.getAbstractFileByPath(filePath);
// Check if it exists and is of the correct type
if (file instanceof TFile) {
// file is automatically casted to TFile within this scope.
}
使用 normalizePath()
清理用户定义的路径
每当您访问库中文件或文件夹的用户定义路径时,或者当您在插件代码中构造自己的路径时,请使用 normalizePath() 。
通过normalizePath()
获取路径并清理它,以确保文件系统和跨平台使用的安全。此功能:
- 清理正斜杠和反斜杠的使用,例如将 1 个或多个
\
或/
替换为单个/
。 - 删除前导和后导斜杠。
- 将任何不间断空格 替换为
\u00A0
常规空格。 - 通过 String.prototype.normalize 执行路径。
import { normalizePath } from "obsidian";
const pathToPlugin = normalizePath(app.vault.configDir + "//plugins/my-plugin");
// pathToPlugin contains ".obsidian/plugins/my-plugin" not .obsidian//plugins/my-plugin
编辑器
更改或重新配置编辑器扩展
如果要在使用 registerEditorExtension(), 注册后更改或重新配置[[编辑器扩展]],请使用 updateOptions() 更新所有编辑器。
class MyPlugin extends Plugin {
private editorExtension: Extension[] = [];
onload() {
//...
this.registerEditorExtension(this.editorExtension);
}
updateEditorExtension() {
// Empty the array while keeping the same reference
// (Don't create a new array here)
this.editorExtension.length = 0;
// Create new editor extension
let myNewExtension = this.createEditorExtension();
// Add it to the array
this.editorExtension.push(myNewExtension);
// Flush the changes to all editors
this.app.workspace.updateOptions();
}
}
TypeScript
使用 const
和 let
优于 var
有关更多信息,请参阅在现代 JavaScript 中 var 被认为过时的 4 个原因。
首选 async/await 而不是 Promise
最新版本的 JavaScript 和 TypeScript 支持 async
and await
关键字异步运行代码,这使代码比用 Promise 更具可读性。
不建议:
function test(): Promise<string | null> {
return requestUrl('https://example.com')
.then(res => res.text
.catch(e => {
console.log(e);
return null;
});
}
请改为执行此操作:
async function AsyncTest(): Promise<string | null> {
try {
let res = await requestUrl('https://example.com');
let text = await r.text;
return text;
}
catch (e) {
console.log(e);
return null;
}
}
考虑使用文件夹组织代码库
如果您的插件使用多个 .ts
文件,请考虑将它们组织到文件夹中,以便于查看和维护。
重命名占位符类名
示例插件包含常见类的占位符名称,例如 MyPlugin
、 MyPluginSettings
和 SampleSettingTab
。重命名这些名称以反映插件的名称。
评论区