Obsidianのプラグイン開発で独自のコードブロックを定義する
Obsidianのプラグイン開発で独自のコードブロックを定義すれば、ノートにいろんな情報を埋め込んで表示できます。
たとえばDataviewであれば絞り込んだノートの一覧、Tasksであればタスクの一覧などです。
ObsidianのAPIに関する公式ドキュメントを読んでも、APIの実際の使い方までは書いていません。
本記事では、APIの実装例を挙げながら「独自のコードブロックを定義して使えるようにする方法」を紹介します。
対象はいうまでもなくのObsidianのプラグイン開発者です。
独自のコードブロックの定義方法
まずは一番単純な例から紹介します。
シンプルに、受け取った内容に対してHello, 〇〇
を表示させてみます。


import { Plugin } from "obsidian";
export default class CreateCodeblock extends Plugin { async onload() { this.registerMarkdownCodeBlockProcessor( "sample", async (source, element, context) => { const container = element.createEl("div"); container.innerText = `Hello, ${source}!`; }, ); }}
プラグインのonload
の中でregisterMarkdownCodeBlockProcessor
を使うことでコードブロックを定義できます。
次のセクションで各引数を詳しくみていきましょう。
第1引数に言語名
第1引数がコードブロックの言語名です。本記事の例では言語名をsample
とします。よって、コードブロックは次のように書きます。
```sampleNinja```
第2引数にハンドラ
第2引数には「コードブロックの中身を受け取って表示させるハンドラ」を渡します。
例で出てきたハンドラの中身を見てみましょう。
async (source, element, context) => { const container = element.createEl("div"); container.innerText = `Hello, ${source}!`;},
source
はコードブロックの中に書いた文字列です。前述の例ではNinja
が代入されます。
element
の中で要素をこねくり回すことで、自由に表示できます。
context
は次のセクションで説明します。
公式ドキュメント:registerMarkdownCodeBlockProcessor - Developer Documentation
コンポーネントのレンダリング
Reactのコンポーネントの表示方法は2パターンあります。
elementを使うパターン
まずはregisterMarkdownCodeBlockProcessor
に渡すハンドラの引数「element
」に対してcreateRoot
を使うパターンです。
this.registerMarkdownCodeBlockProcessor( this._language, async (source, element, context) => { myRender(element, source); },);
import "./app.css";import { createRoot } from "react-dom/client";
export function myRender(containerEl: Element, source: string) { const root = createRoot(containerEl); root.render(<p>Hello, {source}!!!</p>);}
createRootの呼び出しはtsx
ファイルとして切り分けています。
最初からmain.ts
の方をmain.tsx
にして、main.tsxの中にコンポーネントを書くのもいいかもしれませんね。
基本はこっちのパターンで十分だと思います。
contextを使うパターン
続いて、ハンドラの引数context
を使う例です。こちらはライフサイクルを含めたコンポーネントを作りたい時に使います。
まずはさっくりとした例から。App
なるものをcontext.addChild
します。
this.registerMarkdownCodeBlockProcessor( "sample", async (source, element, context) => { const renderer = new App(element, source); context.addChild(renderer); },);
import "./app.css";import { MarkdownRenderChild } from "obsidian";import { type Root, createRoot } from "react-dom/client";
export class App extends MarkdownRenderChild { root: Root; source: string;
constructor(containerEl: HTMLElement, source: string) { super(containerEl); this.root = createRoot(containerEl); this.source = source; }
async onload() { this.root.render(<div>Hello, {this.source}(Component)!!!</div>); }
async onunload() { this.root.unmount(); }}
MarkdownRenderChildを継承したクラスでonload
やonunload
を定義しています。これがライフサイクルの部分です。
具体的に。このパターンを採用しているobsidian-plugin-abcjsでは、onunload
で再生停止の処理をしているようです。
公式ドキュメント:MarkdownRenderChild - Developer Documentation
コードブロックの中身をJSとして処理させる
コードブロックの中身をJavaScriptとして処理する方法も解説します。
registerMarkdownCodeBlockProcessor
に渡すハンドラの引数source
を次のような関数に渡します。
async function execute(source: string) { const func = new Function( `return new Promise((s,r)=>{(async ()=>{return ${source};})().then(s).catch(r)})`, ); return await func();}
このFunction
はFunctionコンストラクタというものです。
戻り値がなければreturn
を外すなど、適宜書き換えてください。
コードブロックの中でAPIを呼び出したい
コードブロックの中でDataviewのような他のプラグインのAPIを呼び出せるようにするには、次のように1行足します。
async function execute(source: string) { const func = new Function( `return new Promise((s,r)=>{(async ()=>{const dv = DataviewAPI;return ${source};})().then(s).catch(r)})`, ); return await func();}
エラーだったら教えてあげたい
コードブロックを扱うということは、入力ミスがありえます。
文法エラーやパースできなかったときに、そのエラー内容を表示してあげると親切です。
次の例がエラーだったらその内容を表示するReactのコンポーネントです。
// ...type Props = { rawOptions: unknown;};
export default function App({ rawOptions }: Props) { const { options, error } = useCodeBlock(rawOptions); // ここでJS実行やパース if (error) { console.log(error); return <pre>{String(error)}</pre>; } return (<MyComponent options={options} />);}
useCodeBlock
の中で「コードブロックの文字列をJavaScriptとして処理」「パース」など行います。
その過程でエラーが出たらその旨を表示するだけです。
シンタックスハイライト
コードブロックはそのままだとただの文字列扱いで、装飾などはされません。

次のようにコードブロックの文字列にシンタックスハイライトをつけることができます。

ではコードを見てみましょう。JavaScriptとしてシンタックスハイライトが効くように設定しました。
import { Plugin } from "obsidian";
export default class CreateCodeblock extends Plugin { private _language = "sample";
async onload() { this.registerMarkdownCodeBlockProcessor( // ... 省略 ); this.registerCodeBlockHighlithing(); this.register(() => this.unregisterCodeBlockHighlithing()); }
registerCodeBlockHighlithing() { window.CodeMirror.defineMode(this._language, (config) => window.CodeMirror.getMode(config, "javascript"), ); } unregisterCodeBlockHighlithing() { window.CodeMirror.defineMode(this._language, (config) => window.CodeMirror.getMode(config, "null"), ); }}
onload
の中でシンタックスハイライトの定義と解除の登録を書きます。
シンタックスハイライト自体はObsidianではなくCodeMirrorでやっています。CodeMirror: Language ModesにCodeMirrorのデフォルトで使える言語が書いてあります。
参考
サンプルリポジトリ
本記事で扱ったサンプルコードは次のリポジトリにあります。
eetann/obsidian-create-codeblock
以上、Obsidianのプラグイン開発におけるコードブロックの扱い方でした。
残念ながら開発中のプラグインでは結局使わないことになりましたが、せっかく調べたので備忘録として残したのでした。ちゃんちゃん。