💡

Neovimでしていたタスク管理をObsidianのQuickAddプラグインでやってみた

に公開

こんにちは。ダイの大冒険エンジョイ勢のbun913と申します。

以前私は以下のような記事を書いて、Neovimで作業記録やタスクの詳細を管理しているTIPSを紹介しました。

https://zenn.dev/moneyforward/articles/e12037a09f436c

構造は単純で、メインは以下のようなmain.mdというファイルにマークダウン構造で記載します。

main.md
# Task Management

## Link

- [Hoge機能の親タスクリンク](https://example.com)
- [安心を与えるためのQA心得的な覚書](./tasks/qa-kokoroe.md)

## Tasks

- [ ] Nvimのタスク管理をZennに書く // このタスクに集中すると決める
- [ ] 9/18 サブ機能Aのテスト分析を終わらせる
- [ ] 新規機能Bの権限関係のテスト実行しとく
- [ ] OSSの更新しなきゃ 

## Stopped

- [ ] Hoge機能のテスト計画から。11月くらいに様子を聞いてみる

ただし、このままではここに色々とメモを書きたくなって整理できなくなるため、1つのタスクに対して1つのマークダウンファイルを作成して、そこに参考資料のリンクやメモ、成果物などのリンクを残すようにしています。

.
├── archives #ここに個別タスクをアーカイブします
│   └── 20250312
│       ├── misc.md #別ファイルに切り出さなかった小さいタスクはここに完了を記録します
│       └── nvimTaskManagementArticle.md # 個別のタスクは別のmdファイルで保管します
├── main.md # 本体のファイル。ここから必要ならtasksにファイルを切り出して詳細を書く
└── tasks #ここに進行中のタスクファイルが入ります
    └── obsidianTaskManagement.md

main.md から tasks/ ディレクトリにファイルを作成して、そこへのリンクを main.md に記録するコマンドを ConvertTask として作成していました。

convertTask

また、終了したタスクをアーカイブ用のディレクトリに移動するコマンドを ArchiveTask として作成していました。

ArchiveTask1

今回はこれらのコマンドをObsidianだけで完結できないかな?と思い QuickAdd プラグインを利用して、同様の機能を実現してみました。

QuickAdd Plugin

QuickAdd は Obsidian の Community Plugin で、ユーザーがカスタムコマンドを作成して、さまざまなアクションを迅速に実行できるようにするものです。単なるテンプレート管理にとどまらず、JavaScript を使用して複雑なロジックをマクロとして組み込むこともできます。

https://github.com/chhoumann/quickadd

他に簡単な方法もあるかもしれませんが、私の場合 JavaScript で色々管理できる方が魅力的なので以下のようなディレクトリ構成にしてタスクやJSコードも含めてgitで管理しています。

vault/
├── scripts/              # QuickAddマクロ用
├── tasks/               # タスクファイル保存先
├── archives/            # アーカイブ用
└── main.md             # メインタスクリスト

JavaScriptファイルの準備

まずは scripts/ ディレクトリに以下のようなJSファイルを配置しておきます。

convertTask.js
module.exports = async (params) => {
	const { app, quickAddApi, obsidian } = params;
	const { MarkdownView } = obsidian;

	const activeView = app.workspace.getActiveViewOfType(MarkdownView);
	if (!activeView) {
		new Notice("No active markdown file");
		return;
	}

	const editor = activeView.editor;
	const cursor = editor.getCursor();
	const line = editor.getLine(cursor.line);

	const taskMatch = line.match(/^(\s*)- \[[ x]\] (.+)$/);
	if (!taskMatch) {
		new Notice("No valid task found on the current line");
		return;
	}

	const indent = taskMatch[1];
	const taskTitle = taskMatch[2];

	const fileName = await quickAddApi.inputPrompt("File name:");
	if (!fileName) {
		new Notice("File name cannot be empty");
		return;
	}

	const tasksFolder = "tasks";
	try {
		if (!app.vault.getAbstractFileByPath(tasksFolder)) {
			await app.vault.createFolder(tasksFolder);
		}
	} catch (error) { }

	const filePath = `${tasksFolder}/${fileName}.md`;
	const content = `# ${taskTitle}

## Reference

## Artifact

---
Created: ${new Date().toISOString().split('T')[0]}
`;

	try {
		if (app.vault.getAbstractFileByPath(filePath)) {
			new Notice(`File already exists: ${filePath}`);
			return;
		}

		await app.vault.create(filePath, content);

		const newLine = `${indent}- [ ] [[${filePath}|${taskTitle}]]`;
		editor.setLine(cursor.line, newLine);

		new Notice(`File created: ${filePath}`);

		const shouldOpen = await quickAddApi.yesNoPrompt("Open the created file?");
		if (shouldOpen) {
			const file = app.vault.getAbstractFileByPath(filePath);
			if (file) {
				await app.workspace.getLeaf('tab').openFile(file);
			}
		}

	} catch (error) {
		new Notice(`Failed to create file: ${error.message}`);
		console.error("ConvertTask error:", error);
	}
};
archiveTask.js
module.exports = async (params) => {
	const { app, obsidian } = params;
	const { MarkdownView } = obsidian;

	const activeView = app.workspace.getActiveViewOfType(MarkdownView);
	if (!activeView) {
		new Notice("No active markdown file");
		return;
	}

	const editor = activeView.editor;
	const cursor = editor.getCursor();
	const line = editor.getLine(cursor.line);

	const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
	const archiveDir = `archives/${date}`;

	try {
		if (!app.vault.getAbstractFileByPath("archives")) {
			await app.vault.createFolder("archives");
		}
		if (!app.vault.getAbstractFileByPath(archiveDir)) {
			await app.vault.createFolder(archiveDir);
		}
	} catch (error) { }

	const linkMatch = line.match(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/);

	if (linkMatch) {
		const filePath = linkMatch[1];
		const file = app.vault.getAbstractFileByPath(filePath);

		if (!file) {
			new Notice(`File not found: ${filePath}`);
			return;
		}

		const fileName = file.name;
		const newPath = `${archiveDir}/${fileName}`;

		try {
			await app.vault.rename(file, newPath);

			let newLine = line.replace(/\[\[([^\]|]+)/, `[[${newPath}`);
			newLine = newLine.replace(/- \[ ?\]/, '- [x]');

			editor.setLine(cursor.line, newLine);

			new Notice(`Task archived: ${newPath}`);
		} catch (error) {
			new Notice(`Failed to move file: ${error.message}`);
			console.error("Archive file error:", error);
		}

	} else {
		const taskMatch = line.match(/^(\s*)- \[([x ])\] (.+)$/);
		if (!taskMatch) {
			new Notice("No valid task found on the current line");
			return;
		}

		const taskText = taskMatch[3];
		const miscPath = `${archiveDir}/misc.md`;

		try {
			let miscContent = `# ${date} misc tasks\n\n`;
			const existingFile = app.vault.getAbstractFileByPath(miscPath);

			if (existingFile) {
				miscContent = await app.vault.read(existingFile);
			}

			miscContent += `- [x] ${taskText}\n`;

			if (existingFile) {
				await app.vault.modify(existingFile, miscContent);
			} else {
				await app.vault.create(miscPath, miscContent);
			}

			editor.replaceRange('',
				{ line: cursor.line, ch: 0 },
				{ line: cursor.line + 1, ch: 0 }
			);

			new Notice(`Task archived to: ${miscPath}`);
		} catch (error) {
			new Notice(`Failed to archive task: ${error.message}`);
			console.error("Archive task error:", error);
		}
	}
};

QuickAdd Pluginの準備

次にObsidianで cmd+, を押して設定を開きます。

左側のメニューから Community plugins を選択して、QuickAdd プラグインをインストールしておきます。

install

インストールできたら、QuickAddの設定画面を開きます。

openSettings

上の方の NameConvertTask などのカスタムマクロのアクション名を入力し、 TemplateMacro に変更して、 Add Choice をクリックします。

addChoice

追加された ConvertTask の歯車マークの設定画面を開き、 User Scripts から先ほど作成していた scripts/convertTask.js を選択して、Add をクリックします。

setScript

これにてスクリプトを実行する用意ができました。

また先ほどのカスタムマクロ(やテンプレート)の一覧で ConvertTask の雷のようなマークをクリックして有効にしておくことで、Obsidianのコマンドパレットから直接実行できるようになります。

enableMacroFromCommandPalette

このように archiveTask などのカスタムマクロも設定できます。

デモ

以下のように Vault 直下にサンプルのマークダウンファイルを用意します。

sampleFile

今回は一番上のタスクの上でコマンドパレットを呼び出し(cmd+P)、「ConvertTask」を選択します。

convertTask

同じように archiveTask を選択することで、アーカイブ処理を実行できます。(archives/ への移動)

archiveTask

まとめ

  • これまでNeovimで行っていたメモやタスクの管理をObsidianに移行しようとしています
  • Neovimのカスタムluaスクリプトを活用していたことをObsidianのQuickAddを活用して同様に実現してみました
    • もっと楽な方法もあるかもしれませんが、ちょっとしたコーディングの知識とAIの活用で問題なく管理できそうです

なお、Obsidianのプラグインを使ってもっとリッチなタスク管理などができそうです。私の場合、メインのスケジュール管理などはJIRAなどのチケットを活用していて、他の人に見せる必要のない細かい手順やメモなどをObsidianで管理しているため、この使い方で当面は問題なさそうです。

TimeRulerプラグインを活用している方もいらっしゃるので、スタイルによって使い分けると良いかもしれません。

https://dev.classmethod.jp/articles/managing-tasks-in-obsidian-with-time-ruler-plugin/

GitHubで編集を提案
Money Forward Developers

Discussion