generated at
メモ帳を表示するPage Menu
やることを一覧できた方が、takkerの使い方にあっているんじゃないか

どういうことか
手書きのメモ帳を使っていた頃は、いつなにをすればいいかがわからなくならなかった
それはやることを一覧できていたからではないだろうか?
「ここをみれば分かる」という場所がある
同様に、scrapboxでもやることを一覧できるようにするのがtakkerの使い方にあっているのではないだろうか?
一定期間運用できていたという実績がある

仕様
指定したprojectから検索する
メモ帳 (scrapbox)も開いて、特定の条件に一致する行をpage menuに表示する
その行がリンクだけの場合は、そのリンク先ページを開くようにする
押すと該当ページに飛ぶ
新しいタブでいいか
reload ボタンを押すと再読み込みが始まる

実装

他の方法
hash tagをつける
関連しないページ同士につけたくないなあtakker
日刊記録sheetに「このページを開いて読め」というタスクを毎日入れる
こっちの方がよさそうではある
既存の道具の組み合わせで実現できる
毎日開くようにすれば、そのうちわからないことが出てきたらすぐそのページを開ける宵になるかもしれない
欠点
🔳つきページを一覧出来ない
これは専用のPage Menuを作らないとだめそうだなー
「Page Menuを毎日開け」という繰り返しタスクと組み合わせればいいのか

dependencies

script.js
import {fetchPages, getFavicon} from '../scrapbox-api-helper/scrapboxAPI.js';

script.js
const id = 'memorandum'; let initialized = false; scrapbox.PageMenu.addMenu({ title: id, image: `https://img.icons8.com/ios/180/${isDark() ? 'FFFFFF/' : ''}notepad.png`, //image: `https://img.icons8.com/ios/180/FFFFFF/notepad.png`, onClick: async () => { if (initialized) return; await reload(); initialized = true; }, });

本体
script.js
const memoPath = {project: 'takker-memex', title: 'メモ帳'}; let loading = false; let timer = false; async function reload() { scrapbox.PageMenu(id).removeAllItems(); scrapbox.PageMenu(id).emitChange(); // 読み込みに時間がかかるようであれば、読み込み中の表示を出す showLoading(); const pending = (async () => { const items = await getMemos(memoPath.project, memoPath.title); clearLoading(); items.forEach(({title, pathname, image}) => scrapbox.PageMenu(id).addItem({ title, image, onClick: () => window.open(`https://scrapbox.io${pathname}`), }) ); })(); const pending2 = (async () => { const items = await getTaskItems(['takker-memex', 'takker'], ({title}) => title.startsWith('🔳')); clearLoading(); items.forEach(({project, title, image}) => scrapbox.PageMenu(id).addItem({ title, image, onClick: () => window.open(`https://scrapbox.io/${project}/${title}`), })); })(); return await Promise.all([pending, pending2]); }

条件に一致するページを取得する
script.js
async function getTaskItems(projects, condition) { return (await Promise.all(projects.map(async project => { const image = await getFavicon(project); return (await fetchPages({project})) .flatMap(page => condition(page) ? [{project, title: page.title, image}] : []); }))).flat(); }

特定のページからメモを取得する
[/icons/hr.icon] から [/icons/hr.icon] までを行IDつきで取得する
空行は無視する
script.js
async function getMemos(project, pageTitle) { const res = await fetch(`/api/pages/${project}/${pageTitle}`); const {lines} = await res.json(); let start = false; return lines.flatMap(({text, id}) => { if (text === '[/icons/hr.icon]') { start = !start; return []; } if (!start) return []; const title = text.trim(); if (title === '') return []; const pathname = /^\[(?:[^=!"#%&'()*+,\-./{|}<>_~]|[=!"#%&'()*+,\-./{|}<>_~]\S)[^\]]+\]$/.test(title) ? `/${project}/${title.match(/^\[([^\]]+)\]$/)[1]}` : `/${project}/${pageTitle}#${id}`; return [{ title, pathname, image: `https://img.icons8.com/ios/180/${isDark() ? 'FFFFFF/' : ''}unchecked-checkbox.png`, }]; }); }

script.js
function showLoading() { loading = true; clearTimeout(timer); timer = setTimeout(() => { loading = true; scrapbox.PageMenu(id).addItem({ title: 'Loading...', image: `https://img.icons8.com/ios/180/${isDark() ? 'FFFFFF/' : ''}loading.png`, onClick: () => {}, }); }, 100, ); } function clearLoading() { clearTimeout(timer); if (loading) { scrapbox.PageMenu(id).removeAllItems(); scrapbox.PageMenu(id).emitChange(); scrapbox.PageMenu(id).addItem({ title: 'Reload', image: `https://img.icons8.com/ios/180/${isDark() ? 'FFFFFF/' : ''}refresh.png`, onClick: () => reload(), }); } loading = false; }

dark theme UserCSS@v.0.2.0を使っているprojectではアイコンの色を白にする
script.js
function isDark() { return ['takker', 'takker-memex',].includes(scrapbox.Project.name); }

#2021-03-20 17:02:26