Obsidian未完成ノート記録プラグイン作成ログ-1

Obsidianプラグイン作成方法の調査ログ-内容 | Obsidian未完成ノート記録プラグイン作成ログ-2
Obsidianプラグイン作成方法の調査
を基にしてプラグインを作成していく。

概要としては
未完成のノートをフロントマターの値などで判断して、リストかテーブル形式にする。
そして、備考みたいなものを記入できるようにしたい
それをサイドにやりたい

まず、sampleをクローンしよう

git clone https://github.com/obsidianmd/obsidian-sample-plugin

そして新しい保管庫を作成する。
これを基にしてみる

以下のコマンドを実行してフォルダを作成する。

cd path/to/vault 
mkdir .obsidian/plugins 
cd .obsidian/plugins

ただこれでは自分のリポジトリを作成できないので以下のリンクを参考してフォークする。

一応このプラグインの名前を決めとこう。
unnoted reason memoとかどうだろう
そして作成

sampleを動かすには
保管庫からpluginディレクトリに移動して
npmで依存関係をインストールする。

npm install

そしてコンパイルする。

npm run dev

新しい保管庫の方でプラグインを有効にする。
ホットリロードも有効にする。

プラグインのマニフェストを更新していく。
main.jsを開く。

そしてプラグインクラスの名前(MyPlugin)を自分のプラグイン名に変更する。

UIはこんな感じで分けられてるらしい

コードを見てたらなんかここで設定を追加してるらしい。

たぶんDEFAULT_SETTING定数で設定した値が取得できるんだと思う。
その上にインターフェースあった。

一旦設定を完成させようかな

  • 使用するフロントマター
  • フロントマターの値
    • これは追加式にしよう

こんな感じで設定画面を作成してるらしい

setNameで何の設定かを明示する。
setDescでその設定の説明文を記入する。
addTextで設定を反映したり、テキストフィールドにプレースホルダーを入れたりできる。


一旦カスタムフレームの設定追加UIの作り方を見に行く。

srcフォルダを作成した。

そのためにesbuild.config.jsファイルとtsconfig.jsonファイルを編集した。
esbuildファイルはentryPointsなるものをファイルのパスに変更。

tsconfigファイルは


baseUrlを編集。

publish: false

ボタンを使いたいならButtonComponent、ドロップアウトメニューを使いたいならDropdwonComponentをインポートする。

import { App, ButtonComponent, DropdownComponent, PluginSettingTab, Setting } from "obsidian";

しっかりとmainファイルもインポートする。

import UnnotedReasonMemo from "./main";

TypeScript ではほかのファイルにクラスをインポートさせたいときに必ずclassキーワードの前にexportキーワードを書かなきゃいけないらしい。

カスタムフレームプラグインではこんな感じで設定を追加するコードが書かれていた。

 let addDiv = this.containerEl.createDiv();
        let dropdown = new DropdownComponent(addDiv);
        dropdown.addOption("new", "Custom");
        for (let key of Object.keys(presets))
            dropdown.addOption(key, presets[key].displayName);
        new ButtonComponent(addDiv)
            .setButtonText("Add Frame")
            .setClass("custom-frames-add")
            .onClick(async () => {
                let option = dropdown.getValue();
                if (option == "new") {
                    this.plugin.settings.frames.push({
                        url: "",
                        displayName: "New Frame",
                        icon: "",
                        hideOnMobile: true,
                        addRibbonIcon: false,
                        openInCenter: false,
                        zoomLevel: 1,
                        forceIframe: false,
                        customCss: "",
                        customJs: ""
                    });
                } else {
                    this.plugin.settings.frames.push(presets[option]);
                }
                await this.plugin.saveSettings();
                this.display();
            });

IconShortCodesではこんな感じ

manageCustomIcons(containerEl: HTMLElement): void {
    if (containerEl.hasChildNodes()) containerEl.empty();
 
    const isPacknameInvalid = (name: string) =>
      !/^[A-Za-z0-9]+$/.test(name) ||
      this.plugin.packManager.isPacknameExists(name);
    new Setting(containerEl)
      .setName("Add new icon pack")
      .setDesc("Reserved names: " + BuiltInIconPacknames.join(", "))
      .then((s) => {
        let button: ButtonComponent | null = null,
          input: TextComponent | null = null;
        s.addText((txt) => {
          txt
            .setPlaceholder("Enter name")
            .onChange((name) => {
              const isInvalid = isPacknameInvalid(name);
              txt.inputEl.toggleClass("invalid", !!name && isInvalid);
              button?.setDisabled(isInvalid);
            })
            .then((txt) => txt.inputEl.addClass("isc-add-pack-input")),
            (input = txt);
        }).addButton(
          (btn) => (
            btn
              .setCta()
              .setIcon("plus-with-circle")
              .onClick(() => {
                const packName = input?.getValue();
                if (!packName) return;
                if (isPacknameInvalid(packName)) {
                  new Notice("This name is invalid.");
                  return;
                }
                this.addNewCustomIconEntry(
                  packName,
                  containerEl,
                ).settingEl.scrollIntoView();
                input?.setValue("");
              }),
            (button = btn)
          ),
        );
      });

ボタンを追加する際にはaddButtonメソッドを使用するらしい。

.addButton((btn) => 
	btn
	.setIcon()
	.setTooltip()
	.setWarning()
	.onClick(() => {
		...
	})
)

setIconはアイコン名を入れる。
以下参照。

setTooltipはユーザがボタンをホバーしたら表示される文章・
setWarningはボタンを赤くして視覚的にわかりやすく表示する。
onClickで処理を記述。
こんな感じが基本形らしい。

このメソッドはSettingクラスからチェーンする。

const setting = new Setting(containerE1)
	.setName()
	.setDesc()
	.addButton()
	.then((s) => { ... })

ContainerElとはPluginSettingTabクラスを継承したクラスにて、displayメソッドの

const { containerEl } = this;

のコードで取得できる、設定画面のHTMLElementである。


プロパティの設定方法だが、

this.plugin.settings.useProperty = value;
await this.plugin.saveSettings();

まず、plugin変数。
mainファイルにあるこれである。

そこに値を入れるような書き方をしてから、非同期saveSettingsメソッドを呼び出す。

publish: false

なんかsetDesc内で改行を使用したい場合はこう書くらしい。

.setDesc(
	createFragment((el) => {
		el.appendText("1行目");
		el.createEl("br");
		el.appendText("2行目");
	}),
)

setDesc内でcreateFragmentを使用して要素を追加していくらしい。


追加機構できた。


TypeScriptの配列、これを忘れずに!
追加した要素を削除するときにはこんな感じで書いた。

this.plugin.settings.targetPropertyValues = 
	this.plugin.settings.targetPropertyValues.filter((ele) => targetPropertyValue == ele);
 
containerEl.removeChild(setting.settingEl);
 
await this.plugin.saveSettings();

settingsettingElが追加されている要素みたい。
これでひとまず、設定は作成できたかも。

このプラグインの要件、てかどんな感じのUIなのかとか決めよう。
右側の部分でリスト表示したいし、![[]]記法で他ノートに動的に埋め込みたい気もある。

‘‘‘URM
...
‘‘‘

で表示できるならこれでもいいけど
その場合はTrackerを参考にしようかな

でもたぶん
URM記法で行こうかな
そしたらデータ記録用ノートが必要になる?
それともSettingクラスにマップで格納できるのかな

今残っているタスクは

  • UI関連
    • UI案
    • コード書く
    • テスト
  • データ取得
    • 特定のプロパティを持つノートを抽出する。
  • データ表示
  • モバイル最適化

UI案

Transclude of Unnoted-reason-memo-UI案

このCardをColumnで表示させたら結構いい感じじゃない?