generated at
今日やるリストを表示するPageMenu
今日やるリストを表示するPage Menu
次に取るべき行動の一覧を表示するPage Menuだと、直近の次に取るべき行動から1年後にやるものまで全部一緒くたにでてきてしまい、非常に見づらかった
今日やるリストだけを表示するようにして、使い勝手を上げてみる

interfaceのアイデア
Page Menuを使う
Page Menuを押したらリストが出る
Page Menuを押したら「今日やるリストを出す」「1週間以内にやることを出す」などのいくつかのボタンがある
それらを押すと、その範囲に絞った結果が表示される
どこに表示すればいいだろうか?
これ実装したいtakker
いろんな期間のタスクを見れるとよい
とりあえず今日やるリストまでは作れたので、これをしばらく使ってから様子見かな
Reload の下に Copy All ボタンをつける
これを押すと、今日やることがリスト形式でコピーされる
format.txt
やり残していること [xxx] 今日やること [yyy] 明日やること [zzz] 今週やること [www]
独自のUIを組み立てる
こっちでやりたくなってきた

以下、「Page Menuを使う」案を採用して作成したUserScriptとなる

実装したいこと
↑のUIにする
これはtakker-workflow@0.0.1中のtoolとして新しく作り直したほうがよさそうだな
projectごとに区切り線で分けるのをやめる
全然使わなった

2022-03-25
22:16:37 group() のテストを書く
これをやったら終わり
23:02:29 終了
結構間違えていた
22:15:29 コピペ機能もできた
期間を細かく分け過ぎな気もするが
とりあえずこれで使ってみる
21:54:37 merged
21:52:53 readLinksBulk にバグが有ったので直す
13:36:47 函数を切り出した
2022-03-15
05:48:05 いいかんじ!
完了したやつを外すの忘れてた
05:48:55 直した
05:42:59 script.ts の型チェック終了
05:25:00 E2Eテストやる
実際にuserscriptを動かしてみることをかっこよく言ってみただけtakker
05:19:07 締切 & いつ頃やるかの両方があったときのテストもいれた
05:12:48 parserのテスト終了
2022-03-14
18:49:42 parserを作っていたが、単に探して羅列するだけならそんな必要ないことに気づいた
たとえば2022-03-14にやるタスクを表示するなら、 @2022-03-14 ~2022-03-14 @2022-w12 のいずれかの文字列を含むリンクを表示すればいい
あ、だめだ。それだと期限切れのタスクを表示できない……
一旦期限切れは無視するか?
過去1週間分の日付を列挙して力業で検索するという手段もある

バグ
[⬜️@2022-03-18 ~を!に置き換える] がタスクとして認識されない
これをテストケースに加えて、テストが通るようにコードを修正しよう
09:22:13 今やったら認識された
serverのDB更新にラグがあっただけだろうか?
まあいいや

script.ts
import { setup } from "./mod.ts"; setup(["takker-memex", "takker"]);

type_check.sh
deno cache -r=https://scrapbox.io https://scrapbox.io/api/code/takker/今日やるリストを表示するPageMenu/mod.ts
mod.ts
/// <reference no-default-lib="true" /> /// <reference lib="esnext" /> /// <reference lib="dom" /> import { classify, Category, list, Task } from "../takker99%2Ftakker-scheduler/workflow.ts"; import { useStatusBar, openInTheSameTab, encodeTitleURI, toTitleLc, } from "../scrapbox-userscript-std/dom.ts"; import type { Scrapbox } from "../scrapbox-jp%2Ftypes/mod.ts"; declare const scrapbox: Scrapbox; const id = "today-next-action"; let loading = false; let initialized: Promise<void>; const dummyImage = "/assets/img/favicon/apple-touch-icon.png";

entory point
mod.ts
export function setup(projects: string[]) {
PageMenuのアイコンをFont Awesome 5 FreeにするためにStyleを追加している
mod.ts
const selector = `head style[data-userscript-name="${id}"]`; document.querySelector(selector)?.remove?.(); const style = document.createElement("style"); style.dataset.userscriptName = id; style.textContent = `a#${id}.tool-btn:hover { text-decoration: none; } a#${id}.tool-btn::before { position: absolute; content: "\\f0ae"; font: 900 20px/46px "Font Awesome 5 Free"; } a#${id}.tool-btn img { opacity: 0; } a#${id}.tool-btn ~ ul a::before { position: absolute; font-family: "Font Awesome 5 Free"; font-weight: 900; } a#${id}.tool-btn ~ ul img { opacity: 0; margin-right: 0; }`; document.head.append(style);
Page Menuを入れる
mod.ts
if (!document.getElementById(id)) { scrapbox.PageMenu.addMenu({ title: id, image: dummyImage, onClick: async () => { initialized ??= load(projects); await initialized; }, }); } }

今日やることを取得する
mod.ts
/** 一度取得したTaskを貯めておく場所 * * reloadでresetされる */ const tasks: { project: string; title: string; task: Task; }[] = []; async function load(projects: string[]) { scrapbox.PageMenu(id).removeAllItems(); loading = true; // 再読み込み用 scrapbox.PageMenu(id).addItem({ title: "Reload", onClick: () => { if (loading) return; load(projects); }, }); // コピペ用 scrapbox.PageMenu(id).addItem({ title: "Copy missed and today actions", onClick: async () => { const today = new Date(); const titles = tasks.map(({ task, title }) => { const category = classify(task, today); return { category, title }; }); const text = [ "今日やること", ...titles.flatMap( ({ category, title }) => category === "today" ? [` [${title}]`] : [] ), "", "やり残していること", ...titles.flatMap( ({ category, title }) => category === "missed" ? [` [${title}]`] : [] ), "", ].join("\n"); try { await navigator.clipboard.writeText(text); } catch(e: unknown) { if (!(e instanceof Error)) throw e; console.error(e); alert(`${e.name} ${e.message}`); } }, }); const buttons: [string, string, Category][] = [ ["Copy tomorrow actions", "明日やること", "tomorrow"], ["Copy this week actions", "今週やること", "in week"], ["Copy next week actions", "来週やること", "in next week"], ["Copy this month actions", "今月やること", "in month"], ["Copy next month actions", "来月やること", "in next month"], ["Copy this year actions", "今年やること", "in year"], ["Copy next year actions", "来年やること", "in next year"], ["Copy someday actions", "いつかやること", "someday"], ["Copy no period actions", "時間情報なし", "no startAt"], ] for (const [button, section, cat] of buttons) { scrapbox.PageMenu(id).addItem({ title: button, onClick: async () => { const today = new Date(); const titles = tasks.map(({ task, title }) => { const category = classify(task, today); return { category, title }; }); const text = [ section, ...titles.flatMap( ({ category, title }) => category === cat ? [` [${title}]`] : [] ), "", ].join("\n"); try { await navigator.clipboard.writeText(text); } catch(e: unknown) { if (!(e instanceof Error)) throw e; console.error(e); alert(`${e.name} ${e.message}`); } }, }); } const { render, dispose } = useStatusBar(); try { const titleLcs = new Set<string>(); // 重複除外用 const today = new Date(); for (const project of projects) { render( { type: "spinner" }, { type: "text", text: `Searching "/${project}" for next actions...`}, ); for await (const { task, title } of list(project)) { const titleLc = toTitleLc(title); if (titleLcs.has(titleLc)) continue; titleLcs.add(titleLc); if (task.status === "✅") continue; if (task.status === "❌") continue; tasks.push({ project, title, task }); const category = classify(task, today); if ( category !== "missed" && category !== "today" ) continue; scrapbox.PageMenu(id).addItem({ title, onClick: () => { if (project !== scrapbox.Project.name) { const path = `https://scrapbox.io/${ project }/${encodeTitleURI(title)}`; window.open(path); return; } openInTheSameTab(project, title); }, }); } if (project === projects[projects.length - 1]) continue; scrapbox.PageMenu(id).addSeparator(); } render( { type: "check-circle" }, { type: "text", text: `Found actions.`}, ); } catch(e: unknown) { render( { type: "exclamation-triangle" }, { type: "text", text: e instanceof Error ? `${e.name} ${e.message}` : `Unknown error! (see developper console)`, }, ); console.error(e); } finally { loading = false; setTimeout(() => dispose(), 1000); } }

タスクを startAt に応じて振り分ける


タスクの書式解析など
別ページに切り出したほうがいいかも
Githubに入れる……までしなくてもいいか
2022-03-18 13:51:14 切り出した

#2022-04-09 10:33:42
#2022-04-08 22:49:29
#2022-03-25 09:11:17
#2022-03-22 18:41:43
#2022-03-18 05:54:23
#2022-03-15 04:27:47
#2022-03-14 13:48:05
#2022-03-10 19:35:24
#2022-03-09 12:01:34