作業時間を🍅でリアルタイムに表示するUserScript
可視化方法として良さげに思えた
2025-02-06 22:01:47 未完了の作業は、1分ごとに🍅の数を更新する
$ deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/takker/作業時間を🍅で表示するUserScript/main.ts
main.ts/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
import { getLineDOM, takeInternalLines } from "jsr:@cosense/std@0.29/browser/dom";
import type { Scrapbox } from "jsr:@cosense/types@0.10/userscript";
import { parse } from "../../takker/takker99%2Ftakker-scheduler/deps.ts";
import { differenceInMinutes } from "npm:date-fns@4/differenceInMinutes";
declare const scrapbox: Scrapbox;
const makeTomatoDom = (): HTMLDivElement => {
const div = document.createElement("div");
div.style.position = "absolute";
div.style.top = "0";
div.style.right = "0";
div.style.textAlign = "right";
return div
};
const tomatoDoms = new Map<string, HTMLDivElement>();
let timer: number | undefined;
const updateTomato = ():void => {
for (const tomatoDom of tomatoDoms.values()) {
tomatoDom.remove();
}
tomatoDoms.clear();
if (scrapbox.Layout !== "page" || scrapbox.Project.name !== "takker-memex") {
scrapbox.removeListener("lines:changed", checkLines);
if (!timer) return;
clearInterval(timer);
timer = undefined;
return;
}
checkLines();
scrapbox.addListener("lines:changed", checkLines);
timer = setInterval(checkLines, 60 * 1000);
};
const checkLines = ():void => {
const lines = takeInternalLines();
for (const line of lines) {
const task = parse(line.text);
if (!task?.record?.start) {
const tomatoDom = tomatoDoms.get(line.id);
tomatoDom?.remove?.();
tomatoDoms.delete(line.id);
continue;
}
const duration = differenceInMinutes(task.record.end ?? new Date(), task.record.start);
const tomatoCount = Math.round(duration / 30);
const indicator =
// maximum is 🍅x20
tomatoCount > 20
? `🍅x${(duration / 30).toFixed(1)}`
: "🍅".repeat(tomatoCount);
const tomatoDom = tomatoDoms.get(line.id) ?? makeTomatoDom();
getLineDOM(line.id)?.append?.(tomatoDom);
tomatoDoms.set(line.id, tomatoDom);
tomatoDom.textContent = indicator;
}
};
updateTomato();
scrapbox.addListener("page:changed", updateTomato);
scrapbox.addListener("lines:changed", updateTomato);