之前花了点时间制作Obsidian的插件:agreatfool/obsidian-post-gallery,稍微积累了点经验,这里留点笔记。不会过于深入,因为我做的插件还是很简单的,也没有用到太多的Obsidian的功能。
丑话说在前面,Obsidian的Plugin相关文档是极其匮乏的,有很多功能你基本上找不到文档对于其的描述,更扯的是一部分概念在文档里也没解释,比如说:leaf
等,导致如果你需要制作功能比较复杂的插件的话,会很困难。最好的方法就是找现在官方网站上列出来的那些community plugin的源码,然后慢慢读。我也搞不懂这些作者是怎么搞清楚那么多细节的,哎。反正我是没做很高级的功能,所以基本上没怎么接触。
obsidianmd/obsidian-api,算是官方的插件文档地址。这个repo里面就只有一个d.ts
文件,描述了Obsidian API的内容。制作插件的时候可以使用这些官方提供的API,实际上Obsidian是基于Electron的一个封装,但官方的API并不会暴露Electron的API给你使用,是基于安全方面的考量:Security of the plugins。
obsidianmd/obsidian-sample-plugin是官方的范例repo,可以clone下来尝试下,里面有最简单的使用范例。
在编码方面,你会需要安装Obsidian API,虽然本质上它也就只是一个typing。
main.ts
import * as LibOs from 'os';
import { MarkdownPostProcessorContext, Plugin } from 'obsidian';
import { MarkdownBlockProcessor } from './lib/md_block';
export default class InPostGalleryPlugin extends Plugin {
async onload(): Promise<void> {
if (LibOs.platform() !== 'darwin') {
console.log(`InPostGalleryPlugin.onload unsupported platform: ${LibOs.platform()}`);
return;
}
console.log('InPostGalleryPlugin.onload');
this.registerMarkdownCodeBlockProcessor('post-gallery', async (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
await new MarkdownBlockProcessor(this.app).process(source, el, ctx);
});
}
}
定义一个 default export 的 class,继承 obsidian 包里的 Plugin
。上面的代码就只响应了onload
事件,在事件中保证插件安装在OSX系统中,并注册一个markdown代码块处理器,在post-gallery
代码块出现的时候进行触发。
md_block.ts
剩下的其他功能代码也非常简单,一共没几行:md_block.ts,可以简单看下。
当时在制作这个插件的时候,虽然功能很简单,但还是遇到了不少问题。最主要的一个问题是代码打包。
因为所有的dep安装(源码)都是通过npm安装的,而代码的import / require
又是在Obsidian (Electron)
环境中运行的,所以很多用来初始化的self execute function: (function(){})()
会侦测错环境,导致最终的lib加载注入错了global:global vs window
。
这里需要使用第三方的打包工具来进行代码打包,比如说webpack,但因为我这个项目实在是太小了,杀鸡牛刀,也不想浪费时间搞一大堆的配置。最后就直接把一些第三方的dep的加载函数给改了下,放到src里,作为自己的源码来进行import,就行了。算是比较tricky
的绕过方法。
举个例子:
原版 jquery
(function (global, factory) {
'use strict';
if (typeof module === 'object' && typeof module.exports === 'object') {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
module.exports = global.document
? factory(global, true)
: function (w) {
if (!w.document) {
throw new Error('jQuery requires a window with a document');
}
return factory(w);
};
} else {
factory(global);
}
// Pass this if window is not defined yet
})(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
// ...
});
修改后
(function (global, factory) {
'use strict';
module.exports = factory(window);
// Pass this if window is not defined yet
})(typeof window !== 'undefined' ? window : this, function (window, noGlobal) {
// ...
});
EOF