MilkScriptで週次レビュー用やったことリストや月次レビュー用やったことリストを表示する

引き続き、これまでに書いたMilkScriptの紹介です。
前回もそうですが、この記事も、この次の記事もずっと「完了したタスクをウィンドウで一覧表示するスクリプト」といったものです。

自動でタスクを追加したり削除したりするのが怖くてあまりやってません。
(一応、スクリプト実行後に一度だけ取り消すことはできます)


さて、本題です。
通常のブラウザで動かすJavascriptとの違いのひとつは入力プロンプトを使えない(んだと思う)ことですが、事前にタスク内のノートに値を入力しておいて、それを読み取らせて条件判断させていくのが、以下のスクリプトです。

週次レビュー用やったことリスト

週次レビューの最中に過去1週間をざっと振り返るためのスクリプト。

プロジェクトタスクの細分化されたタスクはすべてサブタスクとして登録しているので、先週完了させたサブタスクだけを取得するといったものです。
ほかのサブタスクも取得してしまうので、リストで検索を絞り込みするなどしています。

これに『やったことリスト(MS)』という、MilkScriptで使う事前パラメータ入力用のためだけのタスクを用意しておきます。

ここの1行目に『週次レビュー』と入力しておき、次の行で『先週』か『今週』かを入力。
スクリプト実行した日付から先週または今週完了させたサブタスクリストをウィンドウ表示させる……というものです。


//Note内の『やったことリスト(MS)』のノートを調査。先週or今週のやったことリストをノートの内容から判断しリスト表示する
let [completedListTask] = rtm.getTasks('やったことリスト(MS) AND list:Note');
completedListTask =  completedListTask ?? rtm.addTask('やったことリスト(MS) #Note', true);

// ノート内容を調査して、今週と書かれていなければ今週。それ以外、またはノートがない場合のデフォルト値は先週となる
const note = completedListTask.getNotes().find(note => {
    const context = note.getContent().split('\n');
    return context[0].trim() === '週次レビュー' && context[1].trim() === '今週';
});

const week = note ? 'thisWeek' : 'lastWeek';

// 今日の曜日を取得 (0: 日曜日, 1: 月曜日, ..., 6: 土曜日)
const today = new Date();
const todayDayOfWeek = today.getDay();
// 月曜日の日付を計算
const daysToMonday = todayDayOfWeek === 0 ? 6 : todayDayOfWeek - 1;
const startDate = new Date(today);
week === `lastWeek` ? startDate.setDate(today.getDate() - daysToMonday - 7) : startDate.setDate(today.getDate() - daysToMonday);

// 日曜日の日付を計算
const endDate = new Date(startDate);
endDate.setDate(startDate.getDate() + 6);

const taskLists = searchTasks(startDate,endDate)
    .sort((a, b) => a.getCompletedDate() - b.getCompletedDate());

const context = [
    `## サブタスクやったことリスト ${formatDateToYYMMDD(startDate).split(`/`).join(``)}- ${formatDateToYYMMDD(endDate).split(`/`).join(``)}\n`,
    ...taskLists.map(task => `- ${task.getName()} ${formatDateToYYMMDD(task.getCompletedDate())}\n`)
];

rtm.newFile(context.join(``), rtm.MediaType.TEXT, 'CompletedTaskList.md');

// 指定された条件で完了されたタスクを返す関数
function searchTasks(begin, end) {
    const formatDate =
    date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`

    const query = [
    `completedAfter:${formatDate(begin)}`,
    `completedBefore:${formatDate(end)}`,
    `status:completed AND isSubtask:true AND list: Task AND NOT isTagged:false AND NOT tag:works`
    ].join(` AND `);
    return rtm.getTasks(query);
}

// 日付をyy/mm/dd形式に変換する関数
function formatDateToYYMMDD(date) {
    const year = date.getFullYear().toString().slice(-2); 
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0'); 

    return `${year}/${month}/${day}`;
}
                        

『やったことリスト(MS)』というタスクがなければ、スクリプト側で新しく作成しています。
これは当初、当該タスクのノートに新規追加で実行結果を追加していた頃の名残です。

デフォルト値(先週)も設定しているので、当該タスクがなくても実行できるし、追加するノートの格納場所として使う必要もないため、タスクを新たに作る必要もないのですが、なんとなく残しています。

パラメータは『今週』と書かれていない限り、『先週』とするよう結構乱暴な決め方していますので、月曜とか今月とか再来週とか書いても、空白にしておいても、ノートがなくても『先週』のリストが表示されます。

自分ひとりしか使わないので、こんなんでいいかなと。

月次レビュー用やったことリスト

完了させたタスクをリスト毎に一覧表示するためのスクリプトです。
完了日でソート。期間やターゲットとなるリス、除外タグ、繰り返しタスクを除外するかどうかをノートの内容から判断。コピーしてノートアプリに貼り付けるためのものです。

こちらも『やったことリスト(MS)』というタスクのノートからパラメータを読み取っています。
完了タスクリストを調査する期間、リスト、除外タスク、繰り返しタスクを除外するかどうかをノートの内容から判断しています。

備忘録とかこんだてリストとかもRTMに入力しているので、レビュー時にノイズになることが多く、それらのタスクには『#memo』『#kondate』などのタグを付けて、ここで収集されるリストからは除外されるようにしています。


// 受信箱内の『やったことリスト(MS)』タスクを取得。なければ作成する
let [completedListTask] = rtm.getTasks('やったことリスト(MS) AND list:Note');
completedListTask =  completedListTask ?? rtm.addTask('やったことリスト(MS) #Note', true);

//取得したタスクのノートの1行目に『月次レビュー』とあるものを探して取得する。
const note = completedListTask.getNotes().find(note => note.getContent().split(`\n`)[0].trim() === `月次レビュー`);
const context = note ? note.getContent().split(`\n`) : [];

const contextTrue = context.length > 1;

// 定数『now』に現在の日付・時刻を格納する
const now = new Date();

// 開始日を取得して、yymmdd形式でなければ今日の日付からstartDateとendDateを決める
let yymmdd = contextTrue ? parseInt(context[1].match(/\d+/), 10) : ``;
const startDate = yymmdd >= 230000 && yymmdd <= 330000
    ? formatDatefromYYMMDD(yymmdd)
    : new Date(now.getFullYear(), now.getMonth()); // デフォルト値

yymmdd = contextTrue ? parseInt(context[2].match(/\d+/), 10) : ``;

let endDate = yymmdd >= 230000 && yymmdd <= 330000
    ? formatDatefromYYMMDD(yymmdd)
    : new Date(startDate.getFullYear(), startDate.getMonth() + 1); // デフォルト値

endDate = endDate - startDate < 1 ? new Date(startDate.getFullYear(), startDate.getMonth() + 1) : endDate;

// 調査リストを変数『targetList』に格納する。
const targetList = contextTrue && context[3].match(/^調査リスト/)
    ? context[3].split(`: `)[1].split(/[、,,]/).map(list => list.trim())
    : [`Task`, `Reminder`,`Habits`,`CheckList`]; // デフォルト値

// 除外タグをtags[]配列にてタグを抜き出し、整形して変数『excludedTags』に格納
const tags = contextTrue && context[4].match(/^除外タグ/)
    ? context[4].split(': ')[1].split(/[、,,]/).map(tag => tag.trim()) : ``;
const excludedTags = tags.length !== 0
    ? tags.map(tags => `NOT tag:${tags}`).join(` AND `)
    : `NOT tag:nbc AND NOT tag:kondate AND NOT tag:todo AND NOT tag:memo AND NOT tag:diet`;  // デフォルト値

// 繰り返しタスクを除外するかどうかを『excludedRepeting』に格納する yesなら `isRepeating:false`;
const exRepeatFlg = contextTrue && context[5].startsWith(`繰り返しタスクを除外する`) && !context[5].endsWith(`yes`) ? true : false;
const excludedRepeting = exRepeatFlg ? `isRepeating:false` : ``;

// 先月完了させたタスクを取得する。関数『searchTasks』を使用
const noteContent = [];

targetList.forEach(list => {
    const completedLastMonth = searchTasks('completed',startDate,endDate,list);
    if (completedLastMonth.length !== 0 ) {
    noteContent.push(formatNote(completedLastMonth,list));
    }
})

// yymmdd形式を日付に変換
function formatDatefromYYMMDD(yymmdd) {
    const year = Math.floor(yymmdd / 10000) + 2000;
    const month = Math.floor((yymmdd % 10000) / 100);
    const day = yymmdd % 100;

    return new Date(year, month - 1, day);
}

function formatTags(tasks) {
    const tagNames = tasks.getTags().map(tag => tag.getName()).join(` #`);
    if (tasks.getTags().length === 0) {
    return ``;
    } else {
    return ` #${tagNames}`;
    }
}
// 指定された条件で完了されたタスクを返す関数
function searchTasks(queryPrefix, begin, end, list) {
    const formatDate =
    date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`

    const query = [
    `${queryPrefix}After:${formatDate(begin)}`,
    `${queryPrefix}Before:${formatDate(end)}`,
    `${excludedTags}`,
    `List:${list}`,
    `${excludedRepeting}`
    ].join(` AND `);
//  console.log(query);
    return rtm.getTasks(query);
}

// 日付をyy/mm/dd形式に変換する関数
function formatDateToYYMMDD(date) {
    const year = date.getFullYear().toString().slice(-2); 
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0'); 

    return `${year}/${month}/${day}`;
}

function formatNote(tasklist,listname) {
    // 完了させたタスクのソート: 完了日昇順
    tasklist.sort((a, b) => a.getCompletedDate() - b.getCompletedDate());
    
    // 集計結果を定数に格納: ## 〇〇年☓☓月  n Tasks Completed
    const formatTotal =`### ${startDate.getFullYear()-2000}年${startDate.getMonth() + 1}月 ` +
    `${listname}: ${tasklist.length} Tasks Completed\n`;

    // タスク詳細をリスト表示: ’- ’ & タスク名、タグ名、完了日 & 改行
    const completedListNote = [];
    tasklist.forEach(tasks => {
    completedListNote.push(`- ${tasks.getName()} ${formatTags(tasks)} ${formatDateToYYMMDD(tasks.getCompletedDate())}\n`);
    })
    return `${formatTotal}${completedListNote.join(``)}\n`;
}

// やったことリストをウィンドウ表示
rtm.newFile(noteContent.join(``), rtm.MediaType.TEXT, 'MonthlyReviewCompletedList.md');
// console.log(noteContent.join(``));
                        

書いたスクリプトの中で、おそらく一番の長文ですがやっていることは単純です。

わからないことはChatGPT(無料版)に相談しながらやりました。
ずっとJSに挫折し続けていたのですが、AIに教えてもらえるようになるだけでこんなに書けるようになるんだな……と感動しています。
どんなにくだらないことでも、同じ質問をしても、その都度、きちんと教えてもらえてありがたかったです。