Obsidian:テンプレートを選びやすくする

ツール活用

テンプレートは便利

増え続けるテンプレート

メモ/ノート管理アプリ「Obsidian」は便利だ。
特に、ノートによって記載する内容やフォーマットを変える場合、そのテンプレートを使い分けることができる Templater プラグインはほぼ必須といっていいだろう。

似たようなノート群の些細な違いでも、毎回繰り返し発生する手間があるなら、別のテンプレートに分けたくなるのは人情。
ファイルサイズも小さいし、特に邪魔になるものでもないから。

しかしそれゆえにテンプレートは増え続けていく。
新規ノート作成時にどのテンプレートが適当だったか迷うほどに。

選択メニューを作ってみた

そこでノート作成時にテンプレートを選択しやすくなるメニュー用スクリプトを作成してみた。

まずホットキーでテンプレート選択メニューを呼び出す。
テンプレートはグループ分けされていて、該当するグループを選ぶ。

次にそのグループに属するテンプレート一覧が表示される。
このサンプルでは「キャラクター」用テンプレートを選んだ。

ノートを名前を入力してエンター。

「キャラクター」用テンプレートでノートが作成された。

「グループ」「テンプレートの名称」「アイコン」を用いてスムースにテンプレートを選べたと思う。

便利ゆえに煩雑に

テンプレート管理の課題

  • 多種多様なノート作りをすれば、テンプレートの種類も増えていく
  • 例えばフォルダでグループ分けしたとしても、通常のテンプレート選択では膨大な候補一覧に埋もれる
  • 候補の絞り込みにはテンプレートのファイル名を知っておかねばならない
  • 利用頻度の少ないテンプレートの存在を忘れがち

理想的な選択機能

  • グループ→該当テンプレートと、候補一覧の表示数を絞り込む(ステップは増える)
  • ファイル名ではなく、分かりやすい名称とアイコンによる識別
  • 絞り込みや選択を数字キーで代用可能
  • グループやテンプレートも自由に並び替えたい

Capacities というノートアプリが、作成する内容に沿ったテンプレート(アプリではObject)を選ぶスタイルで、これにちょっと惹かれてた。

そしてMaybeFixさんが非公開の私用プラグインを披露されていて、そこにまさに求めていた姿があった。

Obsidian を使い倒すために、ChatGPTと手を組んでプラグインを作りまくるなどしている|MaybeFix
ずいぶん久々の更新になってしまいました。色々と仕事が続いていて、なかなか note に記事を上げられていませんでしたが、その間にも Obsidian の商用ライセンスが無償化され、Obsidian 界隈が盛り上がっている今日このごろです。 ...

スクリプトで解決

ChatGPTに相談

ChatGPTくんに「タイルナビゲーションみたいなメニューを作りたい」と相談をもちかけて、まず出てきたのが以下。

これはリスト型じゃないか、もっと気合いれて考えて!と発破をかけたら、それっぽいものが提示された。

これをベースに好みに仕上げようとしたが、サンプルを触ってみて、考えを変えた。
「タイルメニューはマウス操作を強いられる」「絞り込みや直接項目を選ぶことができない」といったデメリットが目立つから。

タイミングよくMaybeFixさんと少しお話できたが、本人もすでにこの形式のデメリットに気づかれてて、リスト型に変えたとのこと。

結果として上に示した「リスト型」「2ステップ型」の形となった。
Templaterスクリプトと、グループとテンプレートの組み合わせを管理するJSONファイルの2つで構成されている。

  • Script_TemplateMenu.md スクリプト
  • TemplateMenu.json JSONファイル
  • スクリプトとJSON、各種テンプレートはVault直下「/1_Templates/」に置いてあるとします
  • 利用される場合は、ファイル名や「★メニュー構成のJSONファイルとテンプレートフォルダのパスを指定」の箇所を適宜書きかえてください。

Script_TemplateMenu.md

<%* 
async function showMenu(menuItems, title = "メニューを選択") {
    const labels = menuItems.map((item, index) => `${index + 1}. ${item.icon} ${item.label}`);
    const values = menuItems.map(item => item.label);

    // 戻るオプションを追加
    labels.push("0. 🔙 戻る");
    values.push("BACK");

    const selectedLabel = await tp.system.suggester(labels, values);
    return selectedLabel;
}

async function main() {
    const fs = this.app.vault.adapter;
    // ★メニュー構成のJSONファイルとテンプレートフォルダのパスを指定
    const menuFile = "/1_Templates/TemplateMenu.json";
    const templateBasePath = "/1_Templates/";

    // メニュー構成ファイルを読み込む
    const menuData = JSON.parse(await fs.read(menuFile));

    while (true) {
        // 親メニューの選択
        const selectedParentLabel = await showMenu(menuData);

        // キャンセルまたはEscでundefinedが返された場合
        if (!selectedParentLabel || selectedParentLabel === "BACK") {
            return;
        }

        // 選択された親メニューの子メニューを取得
        const parentItem = menuData.find(item => item.label === selectedParentLabel);
        if (!parentItem || !parentItem.children) continue;

        const childItems = parentItem.children;

        // 子メニューの選択
        const selectedChildLabel = await showMenu(childItems, parentItem.label);

        // 戻る処理
        if (!selectedChildLabel || selectedChildLabel === "BACK") {
            continue;
        }

        // テンプレートの読み込みとノート作成
        const childItem = childItems.find(item => item.label === selectedChildLabel);
        if (childItem) {
            const templatePath = templateBasePath + childItem.template;
            const content = await fs.read(templatePath);

            // ノート名を指定
            let fileName = await tp.system.prompt("新しいノートの名前を入力してください");
            // 未入力の場合、自動で名前を生成
            if (!fileName) {
                fileName = "NoTitle_" + tp.date.now("YYYY-MM-DD_HHmmss");
            }
            // 禁止文字を除去
            const filePath = fileName.replace(/[\/\\:*?"<>|]/g, "").replace(/\.md$/, "");
            // ノート作成
            await tp.file.create_new(content, filePath, true);
            return;
        }
    }
}

// メイン処理開始
await main();
%>

TemplateMenu.json

[
    {
        "label": "アイデア",
        "icon": "💡",
        "children": [
            { "label": "キャラクター", "icon": "🧍", "template": "tp_new.md" },
            { "label": "場所", "icon": "📍", "template": "Location.md" },
            { "label": "アイテム", "icon": "⚔️", "template": "Item.md" },
            { "label": "プロット", "icon": "📜", "template": "Plot.md" }
        ]
    },
    {
        "label": "メモ",
        "icon": "📝",
        "children": [
            { "label": "資料", "icon": "📄", "template": "Reference.md" },
            { "label": "雑感", "icon": "😊", "template": "Zakkan.md" },
            { "label": "読書", "icon": "📕", "template": "Reading.md" }
        ]
    },
    {
        "label": "その他",
        "icon": "📂",
        "children": [
            { "label": "疑問", "icon": "❓", "template": "Question.md" },
            { "label": "日記", "icon": "📅", "template": "Diary.md" }
        ]
    }
]

ホットキーに入れて運用しよう

あとはスクリプトが呼び出しやすいように、「Templater」オプションの Templater hotkeys に登録して、「ホットキー」から登録する。

(2025-05-13 03:23修正)Templater: Createではなく Templater: Insertで運用してください。この場合は他のノートが開いている状態が前提です。

操作の手抜き上等

とりあえず思いつく範囲の基本的な操作は取り入れています。

1ステップ目で作成をキャンセルする場合は、ESCキーか「0. 🔙 戻る」を選択。

2ステップ目で1ステップ目に戻る場合も、ESCキーか「0. 🔙 戻る」を選択。
なので、2ステップ目から作成をキャンセルする場合は、ESCキー2回といった感じ。

上下キーやマウスで選択以外に、デフォルト同様に名称による絞り込みも可能。
この時に先頭につけられた番号が使えるので、楽に選べる。

ノートの名前を入力する際に、未入力でもエンターキーを受け付ける。

すると作成日時から仮名のファイル名をつけられた。

もちろんカスタマイズも

TemplateMenu.jsonをメモ帳などで開き、編集が可能。

例えばグループを増やすなら2行目~11行目までのようなセットをコピー&ペーストで増やせば良い。

グループに属するテンプレートも行単位で増やしたり削ったり。
このJSONファイルの並びで表示される順番も反映される。

注意点としては、JSONファイル形式なので、各セットの末尾(赤い丸の箇所)はカンマ不要です。

他にも応用できそう

例えばこんな使い方

  • レビューメモ:書籍や映画など、レビューや記録内容を各テンプレートで。プロパティに書籍ならISBN、映画なら主演などを付加するとか。
  • 創作メモ:キャラクター、プロット、アイデアを広げやすいように、メモ毎の項目を揃えておける。
  • カレンダー:日次・週次・月次のノートはフォーマットが異なるだろう。
  • 住所録:友人知人、仕事関係、ゲーム仲間でテンプレートを変えるなど。
  • 仕事でObsidian:業務の各書式・フォーマット毎にテンプレートを用意。

テンプレートはそれこそ人によって種類も数も異なるだろう。
このスクリプトを使って自分なりのテンプレートライブラリーを充実させてください。