Neovimでしていたタスク管理をObsidianのQuickAddプラグインでやってみた
こんにちは。ダイの大冒険エンジョイ勢のbun913と申します。
以前私は以下のような記事を書いて、Neovimで作業記録やタスクの詳細を管理しているTIPSを紹介しました。
構造は単純で、メインは以下のような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
として作成していました。
また、終了したタスクをアーカイブ用のディレクトリに移動するコマンドを ArchiveTask
として作成していました。
今回はこれらのコマンドをObsidianだけで完結できないかな?と思い QuickAdd
プラグインを利用して、同様の機能を実現してみました。
QuickAdd Plugin
QuickAdd は Obsidian の Community Plugin で、ユーザーがカスタムコマンドを作成して、さまざまなアクションを迅速に実行できるようにするものです。単なるテンプレート管理にとどまらず、JavaScript を使用して複雑なロジックをマクロとして組み込むこともできます。
他に簡単な方法もあるかもしれませんが、私の場合 JavaScript で色々管理できる方が魅力的なので以下のようなディレクトリ構成にしてタスクやJSコードも含めてgitで管理しています。
vault/
├── scripts/ # QuickAddマクロ用
├── tasks/ # タスクファイル保存先
├── archives/ # アーカイブ用
└── main.md # メインタスクリスト
JavaScriptファイルの準備
まずは scripts/
ディレクトリに以下のような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);
}
};
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 プラグインをインストールしておきます。
インストールできたら、QuickAddの設定画面を開きます。
上の方の Name
に ConvertTask
などのカスタムマクロのアクション名を入力し、 Template
を Macro
に変更して、 Add Choice
をクリックします。
追加された ConvertTask
の歯車マークの設定画面を開き、 User Scripts
から先ほど作成していた scripts/convertTask.js
を選択して、Add
をクリックします。
これにてスクリプトを実行する用意ができました。
また先ほどのカスタムマクロ(やテンプレート)の一覧で ConvertTask
の雷のようなマークをクリックして有効にしておくことで、Obsidianのコマンドパレットから直接実行できるようになります。
このように archiveTask
などのカスタムマクロも設定できます。
デモ
以下のように Vault 直下にサンプルのマークダウンファイルを用意します。
今回は一番上のタスクの上でコマンドパレットを呼び出し(cmd+P
)、「ConvertTask」を選択します。
同じように archiveTask
を選択することで、アーカイブ処理を実行できます。(archives/
への移動)
まとめ
- これまでNeovimで行っていたメモやタスクの管理をObsidianに移行しようとしています
- Neovimのカスタムluaスクリプトを活用していたことをObsidianのQuickAddを活用して同様に実現してみました
- もっと楽な方法もあるかもしれませんが、ちょっとしたコーディングの知識とAIの活用で問題なく管理できそうです
なお、Obsidianのプラグインを使ってもっとリッチなタスク管理などができそうです。私の場合、メインのスケジュール管理などはJIRAなどのチケットを活用していて、他の人に見せる必要のない細かい手順やメモなどをObsidianで管理しているため、この使い方で当面は問題なさそうです。
TimeRulerプラグインを活用している方もいらっしゃるので、スタイルによって使い分けると良いかもしれません。
Discussion