見出し画像

【Obsidian】タスクシュートプラグインを改造する


はじめに


Obsidianで、タスクシュートらしきフォーマットでタスク管理をしています。Dynamic Timetableというプラグインを使いつつ、自分が記録したい事項に合わせてフォーマットを作って工夫していて、このことについては以前記事に書きました。


このプラグインでは、計画時に立てた見積時間はタスク完了操作時に実績時間に置き換わるため、記録としては残りません。そのため、記録に残すために見積時間を重複して記載しているわけです。この作業は、慣れればなんということはないのですが、同じ数値をわざわざ2回打ち込むのは若干めんどくさいものがあります。

- [x] タスク : 10 ; 0 @ 22:42 

何より、タスクの中断をする際、自分で追加している見積時間の項目は、タスク名として認識されてコピーされてしまい、毎回書き直しが発生してしまうため、プラグインの良さが消えてしまいます。

- [ ] タスク :10 ; 2 @08:45
- [ ] タスク :10 ; 8

これらの微妙な使い勝手を修正するため、プラグインに修正を加え、自分なりにカスタマイズすることにしてみました。

要件


今実行中のタスクが下記のように記載されているとします。

- [ ] タスク ; 見積時間

ここで、タスク完了ボタンを押下した場合は下記のように記載されるようにしたいです。

- [x] タスク : 見積時間 ;実績時間 @開始時間

中断ボタンを押した時は、下記のようになります。

- [x] タスク : 実績時間(または見積時間) ;実績時間 @開始時間
- [ ] タスク ; 残り時間

実績時間が見積時間よりも短ければ実績時間が、長ければ見積時間が「:」の後に記載され、残り時間(見積時間を超過していれば0)を見積時間とした新たなタスクが生成されます。

ちょっとタスクの記述方法を変更するだけなので、変更箇所がわかればすぐにできそうです。

ソースコードを取得する


まずは修正対象となるソースコードを取得します。Obsidianのプラグインは基本的にはGitHubで管理されているので、Cloneすることで入手します。

今回のDynamic TimeTableのソースコードは、以下のコマンドで取得します。

git clone https://github.com/L7Cy/obsidian-dynamic-timetable.git

今回の修正を実施するためには、ソースコード中の「src/TaskManager.ts」を修正する必要があります。

中身を見ていくと、この中の「updateTaskInContent」関数でタスク完了時及び中断時の書き換えを実行しているようなので、ここを修正します。

修正前のコードは以下。ポイントとなる箇所にコメントを追加しています。

const updateTaskInContent = (
   content: string,
   { elapsedTime, remainingTime }: TaskUpdate
 ): string => {
   //①正規表現で未実行のタスクをキャプチャ
   const taskRegex = new RegExp(
     `^- \\[ \\] (.+?)\\s*${plugin.settings.taskEstimateDelimiter.replace(
       /[.*+?^${}()|[\]\\]/g,
       '\\$&'
     )}\\s*(\\d+\\.?\\d*)?(\\s*@\\s*\\d{1,2}[:]?\\d{2})?(\\s*#.*)?\\s*$`,
     'm'
   );

   const lines = content.split('\n');
   for (let i = 0; i < lines.length; i++) {
     const line = lines[i];
     const taskMatch = line.match(taskRegex);
     if (taskMatch) {
       const originalTaskName = taskMatch[1];
       const tags =
         line
           .match(/\s#([^\s!#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]+)/gu)
           ?.join(' ') || '';
       const actualStartTime = new Date(Date.now() - elapsedTime * 60 * 1000);

// ②タスクを完了時の表記に書き換える
       lines[i] = `- [x] ${originalTaskName.replace(tags, '').trim()} ${
         plugin.settings.taskEstimateDelimiter
       } ${elapsedTime.toFixed(0)} @ ${formatTime(actualStartTime)} ${tags}`;

// ③タスク中断時はタスクを追加する
       if (remainingTime !== undefined) {
         const newTaskToAdd = `- [ ] ${originalTaskName
           .replace(tags, '')
           .trim()} ${
           plugin.settings.taskEstimateDelimiter
         } ${remainingTime.toFixed(0)} ${tags}`;
         lines.splice(i + 1, 0, newTaskToAdd);
       }
       break;
     }
   }
   return lines.join('\n');
 };

①正規表現でタスク記述ルールと一致する行をキャプチャし、各要素を抽出しています。

- 未完了のタスク
- タスク名
- 見積時間
- 開始時刻(オプション)
- タグ(オプション)

②これらの値と実際の開始時刻(現在時刻と経過時間から算出)を使って、行の書き換えを行っています。見積時間については破棄されて実績時間(=経過時間)に置き換わっているので、ここを修正します。

③中断時は、タスク完了時の処理に加え、タスクのコピーが行われます。この時、見積時間は残り時間(remainingTime)となります。ここは同じ処理になるので修正不要です。

すなわち、タスク完了時の処理のみ修正すればOKです。

修正する


まず、修正後のコードを示します。

const updateTaskInContent = (
   content: string,
   { elapsedTime, remainingTime }: TaskUpdate
 ): string => {
  //正規表現で未実行のタスクをキャプチャ
   const taskRegex = new RegExp(
     `^(\\s*)- \\[ \\] (.+?)(?::\\s*(\\d+\\.?\\d*))?\\s*${plugin.settings.taskEstimateDelimiter.replace(
       /[.*+?^${}()|[\]\\]/g,
       '\\$&'
     )}\\s*(\\d+\\.?\\d*)?(\\s*@\\s*\\d{1,2}[:]?\\d{2})?(\\s*#.*)?\\s*$`,
     'm'
   );

   const lines = content.split('\n');
   for (let i = 0; i < lines.length; i++) {
     const line = lines[i];
     const taskMatch = line.match(taskRegex);
     if (taskMatch) {
       const indent = taskMatch[1];
       const originalTaskName = taskMatch[2];
       // ①見積時間のキャプチャ
       const estimateTime = taskMatch[4];
       const tags =
         line
           .match(/\s#([^\s!#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]+)/gu)
           ?.join(' ') || '';
       const actualStartTime = new Date(Date.now() - elapsedTime * 60 * 1000);

     // ②タスク完了時に見積時間を追加
       lines[i] = `${indent}- [x] ${originalTaskName.replace(tags, '').trim()} : ${estimateTime} ${
         plugin.settings.taskEstimateDelimiter
       } ${elapsedTime.toFixed(0)} @ ${formatTime(actualStartTime)} ${tags}`;

       if (remainingTime !== undefined) {
         const newTaskToAdd = `${indent}- [ ] ${originalTaskName
           .replace(tags, '')
           .trim()} ${
           plugin.settings.taskEstimateDelimiter
         } ${remainingTime.toFixed(0)} ${tags}`;
         lines.splice(i + 1, 0, newTaskToAdd);
       }
       break;
     }
   }
   return lines.join('\n');
 };

修正ポイントは2つです。

①見積時間のキャプチャ
修正前では破棄される見積時間をキャプチャする処理を追加しています。

②タスク完了時に見積時間を追加
キャプチャした見積時間を、要件で示したフォーマットに合わせて、「:」の後に追加するよう変更しています。

またついでの修正として、
- インデントが含まれていても認識されるように正規表現を変更。かつ、タスク完了時、中断時にインデントが維持されるように変更。
- 「: 見積時間」の表記がすでに存在しても認識されるように正規表現を変更。

この修正後のコードを使い、実際にObsidianで使えるようにする手順は、次の記事で書こうと思います。

ChatGPTを使ったソースード修正


実は今回の修正はChatGPTの力を大いに借りて行いましたので、その方法を簡単に残しておきます。

この手順ではADA(=Advanced Data Analysis)を使うため、ChatGPT Plusを契約している場合のみ可能な方法であることに注意してください。

まずは、GitHubからクローンしたソースコードを、フォルダごとzipファイルに圧縮します。次に、このzipファイルをChatGPTにアップロードし、下記プロンプトを打ち込み、送信します。
(最高に雑なプロンプトなのでもっと丁寧に書くとより良いです。)

これは、Obsidianのプラグインのソースコードです。まずは中身を解凍して、ソースコードを読んでください。

すると、ADAが走りファイルの解凍と中身の確認が行われます。出力はこんな感じです。

ファイルの解凍が完了しました。以下のファイルが含まれています:

- `src/StatisticsViewComponent.tsx`
- `src/Commands.ts`
- `src/main.ts`
- `src/TaskParser.ts`
- `src/TaskRow.tsx`
- `src/Settings.ts`
- `src/ColorUtils.ts.ts`
- `src/ProgressBar.tsx`
- `src/TimetableViewComponent.tsx`
- `src/TaskManager.ts`
- `src/TimetableView.tsx`
- `src/StatisticsView.tsx`
- `src/Button.tsx`
- `src/BufferTimeRow.tsx`

特定のファイルの内容を確認したい場合は、どれを見たいか教えてください。全体の構造を把握したい場合も、お知らせください。

このあと、修正したい内容を伝えるとどこのファイルのどの部分を修正すべきか修正案含めて提案してくれます。

残念ながら、ChatGPTが出してきた修正案自体はそのまま適用できるようなものでは無かったですが、修正の大まかな方針は示してくれました。また、今回のケースでいえば、コードの解釈や正規表現の書き換え方法を教えてくれたのでサクサクと作業を進めることもできました。

終わりに


この手の開発をしたことがなく、JavaScriptもろくに理解していない自分でも、ChatGPTを活用することで割とスムーズに作業できました。

1から作れと言われると流石に厳しいですが、既存のものをカスタマイズするぐらいであれば何とかなりそうです。

使って行くうちに自分用にフィットさせたくなるものって結構あると思うので、どんどん挑戦していきたいところです。

前述の通り、次の記事ではObsidianのプラグインを実際に使える状態にするところを書きたいと思います。

続きはこちら↓

いいなと思ったら応援しよう!