/// /// /// 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; const dummyImage = "/assets/img/favicon/apple-touch-icon.png"; export function setup(projects: string[]) { 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); if (!document.getElementById(id)) { scrapbox.PageMenu.addMenu({ title: id, image: dummyImage, onClick: async () => { initialized ??= load(projects); await initialized; }, }); } } /** 一度取得した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(); // 重複除外用 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); } }